HADOOP-14077. Add ability to access jmx via proxy. Contributed by Yuanbo Liu.

This commit is contained in:
Eric Yang 2017-02-18 18:34:13 -08:00
parent 3a2e30fa9f
commit 172b23af33
4 changed files with 114 additions and 86 deletions

View File

@ -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 <code>DO_AS</code> 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;
}
};

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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<ApplicationReport> () {
@ -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<ApplicationAttemptReport> attempts;
try {
final GetApplicationAttemptsRequest request =
GetApplicationAttemptsRequest.newInstance(appID);
attempts = callerUGI.doAs(
new PrivilegedExceptionAction<Collection<
ApplicationAttemptReport>>() {
@Override
public Collection<ApplicationAttemptReport> 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 ? "<Not set>"
: app.getAmNodeLabelExpression());
Collection<ApplicationAttemptReport> attempts;
try {
final GetApplicationAttemptsRequest request =
GetApplicationAttemptsRequest.newInstance(appID);
if (callerUGI == null) {
attempts = appBaseProt.getApplicationAttempts(request)
.getApplicationAttemptList();
} else {
attempts = callerUGI.doAs(
new PrivilegedExceptionAction<Collection<ApplicationAttemptReport>> () {
@Override
public Collection<ApplicationAttemptReport> 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,