diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/pom.xml hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/pom.xml
index 91dc26c..9185dc4 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/pom.xml
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/pom.xml
@@ -194,6 +194,13 @@
+ org.apache.hadoop
+ hadoop-minikdc
+ test
+
+
+
+
com.sun.jersey.jersey-test-framework
jersey-test-framework-grizzly2
test
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java 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 62e7f9f..d9bd5a4 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java
@@ -18,9 +18,12 @@
package org.apache.hadoop.yarn.server.resourcemanager.webapp;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
import java.io.IOException;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.AccessControlException;
+import java.security.Principal;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.Collection;
@@ -35,7 +38,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
+import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
@@ -50,11 +55,22 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.io.Text;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
+import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
import org.apache.hadoop.security.authorize.AuthorizationException;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.security.token.SecretManager.InvalidToken;
+import org.apache.hadoop.yarn.api.protocolrecords.CancelDelegationTokenRequest;
+import org.apache.hadoop.yarn.api.protocolrecords.CancelDelegationTokenResponse;
import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationsRequest;
+import org.apache.hadoop.yarn.api.protocolrecords.GetDelegationTokenRequest;
+import org.apache.hadoop.yarn.api.protocolrecords.GetDelegationTokenResponse;
import org.apache.hadoop.yarn.api.protocolrecords.KillApplicationRequest;
import org.apache.hadoop.yarn.api.protocolrecords.KillApplicationResponse;
+import org.apache.hadoop.yarn.api.protocolrecords.RenewDelegationTokenRequest;
+import org.apache.hadoop.yarn.api.protocolrecords.RenewDelegationTokenResponse;
import org.apache.hadoop.yarn.api.records.ApplicationAccessType;
import org.apache.hadoop.yarn.api.records.ApplicationId;
import org.apache.hadoop.yarn.api.records.ApplicationReport;
@@ -63,10 +79,12 @@
import org.apache.hadoop.yarn.api.records.NodeState;
import org.apache.hadoop.yarn.api.records.QueueACL;
import org.apache.hadoop.yarn.api.records.YarnApplicationState;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.exceptions.YarnException;
import org.apache.hadoop.yarn.exceptions.YarnRuntimeException;
import org.apache.hadoop.yarn.factories.RecordFactory;
import org.apache.hadoop.yarn.factory.providers.RecordFactoryProvider;
+import org.apache.hadoop.yarn.security.client.RMDelegationTokenIdentifier;
import org.apache.hadoop.yarn.server.resourcemanager.RMAuditLogger;
import org.apache.hadoop.yarn.server.resourcemanager.RMAuditLogger.AuditConstants;
import org.apache.hadoop.yarn.server.resourcemanager.RMServerUtils;
@@ -88,6 +106,7 @@
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ClusterInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ClusterMetricsInfo;
+import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.DelegationToken;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.FairSchedulerInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.FifoSchedulerInfo;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.NodeInfo;
@@ -95,6 +114,7 @@
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.utils.BuilderUtils;
import org.apache.hadoop.yarn.util.ConverterUtils;
import org.apache.hadoop.yarn.webapp.BadRequestException;
import org.apache.hadoop.yarn.webapp.NotFoundException;
@@ -116,6 +136,9 @@
private final Configuration conf;
private @Context HttpServletResponse response;
+ public final static String DELEGATION_TOKEN_HEADER =
+ "Hadoop-YARN-RM-Delegation-Token";
+
@Inject
public RMWebServices(final ResourceManager rm, Configuration conf) {
this.rm = rm;
@@ -749,8 +772,18 @@ private RMApp getRMAppForAppId(String appId) {
private UserGroupInformation getCallerUserGroupInformation(
HttpServletRequest hsr) {
+ return getCallerUserGroupInformation(hsr, false);
+ }
+
+ private UserGroupInformation getCallerUserGroupInformation(
+ HttpServletRequest hsr, boolean usePrincipal) {
String remoteUser = hsr.getRemoteUser();
+ if (usePrincipal == true) {
+ Principal princ = hsr.getUserPrincipal();
+ remoteUser = princ == null ? null : princ.getName();
+ }
+
UserGroupInformation callerUGI = null;
if (remoteUser != null) {
callerUGI = UserGroupInformation.createRemoteUser(remoteUser);
@@ -758,4 +791,241 @@ private UserGroupInformation getCallerUserGroupInformation(
return callerUGI;
}
+
+ private UserGroupInformation createKerberosUserGroupInformation(
+ HttpServletRequest hsr) throws AuthorizationException, YarnException {
+
+ UserGroupInformation callerUGI = getCallerUserGroupInformation(hsr, true);
+ if (callerUGI == null) {
+ String msg = "Unable to obtain user name, user not authenticated";
+ throw new AuthorizationException(msg);
+ }
+
+ String authType = hsr.getAuthType();
+ if (KerberosAuthenticationHandler.TYPE.equals(authType) == false) {
+ String msg =
+ "Delegation token operations can only be carried out on a "
+ + "Kerberos authenticated channel";
+ throw new YarnException(msg);
+ }
+
+ callerUGI.setAuthenticationMethod(AuthenticationMethod.KERBEROS);
+ return callerUGI;
+ }
+
+ @POST
+ @Path("/delegation-token")
+ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ @Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ public Response postDelegationToken(DelegationToken tokenData,
+ @Context HttpServletRequest hsr) throws AuthorizationException,
+ IOException, InterruptedException, Exception {
+
+ init();
+ UserGroupInformation callerUGI;
+ try {
+ callerUGI = createKerberosUserGroupInformation(hsr);
+ } catch (YarnException ye) {
+ return Response.status(Status.FORBIDDEN).entity(ye.getMessage()).build();
+ }
+ if (tokenData.getToken().isEmpty()) {
+ return createDelegationToken(tokenData, hsr, callerUGI);
+ }
+
+ return renewDelegationToken(tokenData, hsr, callerUGI);
+ }
+
+ private Response createDelegationToken(DelegationToken tokenData,
+ HttpServletRequest hsr, UserGroupInformation callerUGI)
+ throws AuthorizationException, IOException, InterruptedException,
+ Exception {
+ if (tokenData.getRenewer().isEmpty()) {
+ throw new BadRequestException(
+ "Renewer must be specified in the request body");
+ }
+ final String renewer = tokenData.getRenewer();
+ GetDelegationTokenResponse resp;
+ try {
+ resp =
+ callerUGI
+ .doAs(new PrivilegedExceptionAction() {
+ @Override
+ public GetDelegationTokenResponse run() throws IOException,
+ YarnException {
+ GetDelegationTokenRequest createReq =
+ GetDelegationTokenRequest.newInstance(renewer);
+ return rm.getClientRMService().getDelegationToken(createReq);
+ }
+ });
+ } catch (Exception e) {
+ LOG.info("Create delegation token request failed", e);
+ throw e;
+ }
+
+ Token tk =
+ new Token(resp.getRMDelegationToken()
+ .getIdentifier().array(), resp.getRMDelegationToken().getPassword()
+ .array(), new Text(resp.getRMDelegationToken().getKind()), new Text(
+ resp.getRMDelegationToken().getService()));
+ RMDelegationTokenIdentifier ident = new RMDelegationTokenIdentifier();
+ ByteArrayInputStream buf = new ByteArrayInputStream(tk.getIdentifier());
+ DataInputStream in = new DataInputStream(buf);
+ ident.readFields(in);
+ long tokenRenewInterval =
+ conf.getLong(YarnConfiguration.DELEGATION_TOKEN_RENEW_INTERVAL_KEY,
+ YarnConfiguration.DELEGATION_TOKEN_RENEW_INTERVAL_DEFAULT);
+ long currentExpiration = ident.getIssueDate() + tokenRenewInterval;
+ long maxValidity = ident.getMaxDate();
+ DelegationToken respToken =
+ new DelegationToken(tk.encodeToUrlString(), callerUGI.getUserName(),
+ renewer, tk.getKind().toString(), currentExpiration, maxValidity);
+ return Response.status(Status.OK).entity(respToken).build();
+ }
+
+ private Response renewDelegationToken(DelegationToken tokenData,
+ HttpServletRequest hsr, UserGroupInformation callerUGI)
+ throws AuthorizationException, IOException, InterruptedException,
+ Exception {
+
+ if (tokenData.getToken().isEmpty()) {
+ throw new BadRequestException("Empty token in request");
+ }
+ Token token =
+ extractToken(tokenData.getToken());
+ RMDelegationTokenIdentifier ident = new RMDelegationTokenIdentifier();
+ ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
+ DataInputStream in = new DataInputStream(buf);
+ ident.readFields(in);
+
+ org.apache.hadoop.yarn.api.records.Token dToken =
+ BuilderUtils.newDelegationToken(token.getIdentifier(), token.getKind()
+ .toString(), token.getPassword(), token.getService().toString());
+ final RenewDelegationTokenRequest req =
+ RenewDelegationTokenRequest.newInstance(dToken);
+
+ RenewDelegationTokenResponse resp;
+ try {
+ resp =
+ callerUGI
+ .doAs(new PrivilegedExceptionAction() {
+ @Override
+ public RenewDelegationTokenResponse run() throws IOException,
+ YarnException {
+ return rm.getClientRMService().renewDelegationToken(req);
+ }
+ });
+ } catch (UndeclaredThrowableException ue) {
+ if (ue.getCause() instanceof YarnException) {
+ if (ue.getCause().getCause() instanceof InvalidToken) {
+ throw new BadRequestException(ue.getCause().getCause().getMessage());
+ } else if (ue.getCause().getCause() instanceof
+ org.apache.hadoop.security.AccessControlException) {
+ return Response.status(Status.FORBIDDEN)
+ .entity(ue.getCause().getCause().getMessage()).build();
+ }
+ LOG.info("Renew delegation token request failed", ue);
+ throw ue;
+ }
+ LOG.info("Renew delegation token request failed", ue);
+ throw ue;
+ } catch (Exception e) {
+ LOG.info("Renew delegation token request failed", e);
+ throw e;
+ }
+ long renewTime = resp.getNextExpirationTime();
+
+ ident = new RMDelegationTokenIdentifier();
+ buf = new ByteArrayInputStream(token.getIdentifier());
+ in = new DataInputStream(buf);
+ ident.readFields(in);
+
+ DelegationToken respToken =
+ new DelegationToken(token.encodeToUrlString(), ident.getOwner()
+ .toString(), ident.getRenewer().toString(), token.getKind()
+ .toString(), renewTime, ident.getMaxDate());
+
+ return Response.status(Status.OK).entity(respToken).build();
+ }
+
+ @DELETE
+ @Path("/delegation-token")
+ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ public Response cancelDelegationToken(@Context HttpServletRequest hsr)
+ throws AuthorizationException, IOException, InterruptedException,
+ Exception {
+
+ init();
+ UserGroupInformation callerUGI;
+ try {
+ callerUGI = createKerberosUserGroupInformation(hsr);
+ } catch (YarnException ye) {
+ return Response.status(Status.FORBIDDEN).entity(ye.getMessage()).build();
+ }
+
+ Token token = extractToken(hsr);
+ RMDelegationTokenIdentifier ident = new RMDelegationTokenIdentifier();
+ ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
+ DataInputStream in = new DataInputStream(buf);
+ ident.readFields(in);
+
+ org.apache.hadoop.yarn.api.records.Token dToken =
+ BuilderUtils.newDelegationToken(token.getIdentifier(), token.getKind()
+ .toString(), token.getPassword(), token.getService().toString());
+ final CancelDelegationTokenRequest req =
+ CancelDelegationTokenRequest.newInstance(dToken);
+
+ try {
+ callerUGI
+ .doAs(new PrivilegedExceptionAction() {
+ @Override
+ public CancelDelegationTokenResponse run() throws IOException,
+ YarnException {
+ return rm.getClientRMService().cancelDelegationToken(req);
+ }
+ });
+ } catch (UndeclaredThrowableException ue) {
+ if (ue.getCause() instanceof YarnException) {
+ if (ue.getCause().getCause() instanceof InvalidToken) {
+ throw new BadRequestException(ue.getCause().getCause().getMessage());
+ } else if (ue.getCause().getCause() instanceof
+ org.apache.hadoop.security.AccessControlException) {
+ return Response.status(Status.FORBIDDEN)
+ .entity(ue.getCause().getCause().getMessage()).build();
+ }
+ LOG.info("Renew delegation token request failed", ue);
+ throw ue;
+ }
+ LOG.info("Renew delegation token request failed", ue);
+ throw ue;
+ } catch (Exception e) {
+ LOG.info("Renew delegation token request failed", e);
+ throw e;
+ }
+
+ return Response.status(Status.OK).build();
+ }
+
+ private Token extractToken(
+ HttpServletRequest request) {
+ String encodedToken = request.getHeader(DELEGATION_TOKEN_HEADER);
+ if (encodedToken == null) {
+ String msg =
+ "Header '" + DELEGATION_TOKEN_HEADER
+ + "' containing encoded token not found";
+ throw new BadRequestException(msg);
+ }
+ return extractToken(encodedToken);
+ }
+
+ private Token extractToken(String encodedToken) {
+ Token token =
+ new Token();
+ try {
+ token.decodeFromUrlString(encodedToken);
+ } catch (Exception ie) {
+ String msg = "Could not decode encoded token";
+ throw new BadRequestException(msg);
+ }
+ return token;
+ }
}
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/DelegationToken.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/DelegationToken.java
new file mode 100644
index 0000000..1a82ffa
--- /dev/null
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/dao/DelegationToken.java
@@ -0,0 +1,103 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.yarn.server.resourcemanager.webapp.dao;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "delegation-token")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class DelegationToken {
+
+ String token;
+ String owner;
+ String renewer;
+ String kind;
+ @XmlElement(name = "current-expiration")
+ long currentExpiration;
+ @XmlElement(name = "max-validity")
+ long maxValidity;
+
+ public DelegationToken() {
+ token = "";
+ owner = "";
+ renewer = "";
+ }
+
+ public DelegationToken(String token, String owner, String renewer,
+ String kind, long currentExpiration, long maxValidity) {
+ this.token = token;
+ this.owner = owner;
+ this.renewer = renewer;
+ this.kind = kind;
+ this.currentExpiration = currentExpiration;
+ this.maxValidity = maxValidity;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public String getOwner() {
+ return owner;
+ }
+
+ public String getRenewer() {
+ return renewer;
+ }
+
+ public String getKind() {
+ return kind;
+ }
+
+ public long getCurrentExpiration() {
+ return currentExpiration;
+ }
+
+ public long getMaxValidity() {
+ return maxValidity;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public void setOwner(String owner) {
+ this.owner = owner;
+ }
+
+ public void setRenewer(String renewer) {
+ this.renewer = renewer;
+ }
+
+ public void setKind(String kind) {
+ this.kind = kind;
+ }
+
+ public void setCurrentExpiration(long currentExpiration) {
+ this.currentExpiration = currentExpiration;
+ }
+
+ public void setMaxValidity(long maxValidity) {
+ this.maxValidity = maxValidity;
+ }
+
+}
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/KerberosTestUtils.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/KerberosTestUtils.java
new file mode 100644
index 0000000..0b8cc5d
--- /dev/null
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/KerberosTestUtils.java
@@ -0,0 +1,122 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License. See accompanying LICENSE file.
+ */
+package org.apache.hadoop.yarn.server.resourcemanager.webapp;
+
+import javax.security.auth.Subject;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+
+import org.apache.hadoop.security.authentication.util.KerberosUtil;
+
+import java.io.File;
+import java.security.Principal;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.UUID;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * Test helper class for Java Kerberos setup.
+ */
+public class KerberosTestUtils {
+ private static String keytabFile = new File(System.getProperty("test.dir", "target"),
+ UUID.randomUUID().toString()).toString();
+
+ public static String getRealm() {
+ return "EXAMPLE.COM";
+ }
+
+ public static String getClientPrincipal() {
+ return "client@EXAMPLE.COM";
+ }
+
+ public static String getServerPrincipal() {
+ return "HTTP/localhost@EXAMPLE.COM";
+ }
+
+ public static String getKeytabFile() {
+ return keytabFile;
+ }
+
+ private static class KerberosConfiguration extends Configuration {
+ private String principal;
+
+ public KerberosConfiguration(String principal) {
+ this.principal = principal;
+ }
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map options = new HashMap();
+ options.put("keyTab", KerberosTestUtils.getKeytabFile());
+ options.put("principal", principal);
+ options.put("useKeyTab", "true");
+ options.put("storeKey", "true");
+ options.put("doNotPrompt", "true");
+ options.put("useTicketCache", "true");
+ options.put("renewTGT", "true");
+ options.put("refreshKrb5Config", "true");
+ options.put("isInitiator", "true");
+ String ticketCache = System.getenv("KRB5CCNAME");
+ if (ticketCache != null) {
+ options.put("ticketCache", ticketCache);
+ }
+ options.put("debug", "true");
+
+ return new AppConfigurationEntry[]{
+ new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(),
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+ options),};
+ }
+ }
+
+ public static T doAs(String principal, final Callable callable) throws Exception {
+ LoginContext loginContext = null;
+ try {
+ Set principals = new HashSet();
+ principals.add(new KerberosPrincipal(KerberosTestUtils.getClientPrincipal()));
+ Subject subject = new Subject(false, principals, new HashSet