diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
index 59e108a..39cadc9 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
@@ -263,6 +263,17 @@
public static final String RM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY =
RM_PREFIX + "webapp.spnego-keytab-file";
+ /**
+ * Flag to enable override of the default kerberos authentication filter with
+ * the YARN authentication filter to allow authentication using delegation
+ * tokens(fallback to kerberos if the tokens are missing). Only applicable
+ * when the http authentication type is kerberos.
+ */
+ public static final String RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER = RM_PREFIX
+ + "webapp.delegation-token-auth-filter.enabled";
+ public static final boolean DEFAULT_RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER =
+ true;
+
/** How long to wait until a container is considered dead.*/
public static final String RM_CONTAINER_ALLOC_EXPIRY_INTERVAL_MS =
RM_PREFIX + "rm.container-allocation.expiry-interval-ms";
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
index 8bc49e6..9860e00 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
@@ -195,6 +195,15 @@
+ Flag to enable override of the default kerberos authentication
+ filter with the YARN authentication filter to allow authentication using
+ delegation tokens(fallback to kerberos if the tokens are missing). Only
+ applicable when the http authentication type is kerberos.
+ yarn.resourcemanager.webapp.delegation-token-auth-filter.enabled
+ true
+
+
+
How long to wait until a node manager is considered dead.
yarn.nm.liveness-monitor.expiry-interval-ms
600000
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/security/http/YarnAuthenticationFilter.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/security/http/YarnAuthenticationFilter.java
new file mode 100644
index 0000000..db74b16
--- /dev/null
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/security/http/YarnAuthenticationFilter.java
@@ -0,0 +1,41 @@
+package org.apache.hadoop.yarn.server.security.http;
+
+import java.util.Properties;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+
+import org.apache.hadoop.classification.InterfaceAudience.Private;
+import org.apache.hadoop.classification.InterfaceStability.Unstable;
+import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
+
+@Private
+@Unstable
+public class YarnAuthenticationFilter extends AuthenticationFilter {
+
+ public static final String AUTH_HANDLER_PROPERTY =
+ "yarn.resourcemanager.authentication-handler";
+
+ public YarnAuthenticationFilter() {
+ }
+
+ @Override
+ protected Properties getConfiguration(String configPrefix,
+ FilterConfig filterConfig) throws ServletException {
+
+ // In yarn-site.xml, we can simply set type to "kerberos". However, we need
+ // to replace the name here to use the customized Kerberos + DT service
+ // instead of the standard Kerberos handler.
+
+ Properties properties = super.getConfiguration(configPrefix, filterConfig);
+ String yarnAuthHandler = properties.getProperty(AUTH_HANDLER_PROPERTY);
+ if (yarnAuthHandler == null || yarnAuthHandler.isEmpty()) {
+ throw new ServletException("Authentication handler class is empty");
+ }
+ if (properties.getProperty(AUTH_TYPE).toLowerCase().equals("kerberos")) {
+ properties.setProperty(AUTH_TYPE, yarnAuthHandler);
+ }
+ return properties;
+ }
+
+}
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/security/http/YarnAuthenticationFilterInitializer.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/security/http/YarnAuthenticationFilterInitializer.java
new file mode 100644
index 0000000..187f2a6
--- /dev/null
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/security/http/YarnAuthenticationFilterInitializer.java
@@ -0,0 +1,101 @@
+package org.apache.hadoop.yarn.server.security.http;
+
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.hadoop.classification.InterfaceStability.Unstable;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.http.FilterContainer;
+import org.apache.hadoop.http.FilterInitializer;
+import org.apache.hadoop.http.HttpServer2;
+import org.apache.hadoop.security.SecurityUtil;
+import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
+import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
+
+@Unstable
+public class YarnAuthenticationFilterInitializer extends FilterInitializer {
+
+ String configPrefix;
+ String signatureSecretFileProperty;
+ String kerberosPrincipalProperty;
+ String cookiePath;
+
+ public YarnAuthenticationFilterInitializer() {
+ this.configPrefix = "hadoop.http.authentication.";
+ this.signatureSecretFileProperty =
+ AuthenticationFilter.SIGNATURE_SECRET + ".file";
+ this.kerberosPrincipalProperty = KerberosAuthenticationHandler.PRINCIPAL;
+ this.cookiePath = "/";
+ }
+
+ protected Map createFilterConfig(Configuration conf) {
+ Map filterConfig = new HashMap();
+
+ // setting the cookie path to root '/' so it is used for all resources.
+ filterConfig.put(AuthenticationFilter.COOKIE_PATH, cookiePath);
+
+ for (Map.Entry entry : conf) {
+ String name = entry.getKey();
+ if (name.startsWith(configPrefix)) {
+ String value = conf.get(name);
+ name = name.substring(configPrefix.length());
+ filterConfig.put(name, value);
+ }
+ }
+
+ String signatureSecretFile = filterConfig.get(signatureSecretFileProperty);
+ if (signatureSecretFile == null) {
+ throw new RuntimeException("Undefined property: "
+ + signatureSecretFileProperty);
+ }
+
+ Reader reader = null;
+ try {
+ StringBuilder secret = new StringBuilder();
+ reader =
+ new InputStreamReader(new FileInputStream(signatureSecretFile),
+ "UTF-8");
+ int c = reader.read();
+ while (c > -1) {
+ secret.append((char) c);
+ c = reader.read();
+ }
+ filterConfig
+ .put(AuthenticationFilter.SIGNATURE_SECRET, secret.toString());
+ } catch (IOException ex) {
+ throw new RuntimeException("Could not read HTTP signature secret file: "
+ + signatureSecretFile);
+ } finally {
+ IOUtils.closeQuietly(reader);
+ }
+
+ // Resolve _HOST into bind address
+ String bindAddress = conf.get(HttpServer2.BIND_ADDRESS);
+ String principal = filterConfig.get(kerberosPrincipalProperty);
+ if (principal != null) {
+ try {
+ principal = SecurityUtil.getServerPrincipal(principal, bindAddress);
+ } catch (IOException ex) {
+ throw new RuntimeException(
+ "Could not resolve Kerberos principal name: " + ex.toString(), ex);
+ }
+ filterConfig.put(KerberosAuthenticationHandler.PRINCIPAL, principal);
+ }
+ return filterConfig;
+ }
+
+ @Override
+ public void initFilter(FilterContainer container, Configuration conf) {
+
+ Map filterConfig = createFilterConfig(conf);
+ container.addFilter("YARNAuthenticationFilter",
+ YarnAuthenticationFilter.class.getName(), filterConfig);
+ }
+
+}
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
index e2deaa1..666fca7 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
@@ -30,13 +30,16 @@
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.ha.HAServiceProtocol;
import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.apache.hadoop.metrics2.source.JvmMetrics;
+import org.apache.hadoop.security.AuthenticationFilterInitializer;
import org.apache.hadoop.security.Groups;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
import org.apache.hadoop.security.authorize.ProxyUsers;
import org.apache.hadoop.service.AbstractService;
import org.apache.hadoop.service.CompositeService;
@@ -88,8 +91,12 @@
import org.apache.hadoop.yarn.server.resourcemanager.scheduler.event.SchedulerEventType;
import org.apache.hadoop.yarn.server.resourcemanager.security.DelegationTokenRenewer;
import org.apache.hadoop.yarn.server.resourcemanager.security.QueueACLsManager;
+import org.apache.hadoop.yarn.server.resourcemanager.security.RMAuthenticationHandler;
import org.apache.hadoop.yarn.server.resourcemanager.webapp.RMWebApp;
import org.apache.hadoop.yarn.server.security.ApplicationACLsManager;
+import org.apache.hadoop.yarn.server.security.http.YarnAuthenticationFilter;
+import org.apache.hadoop.yarn.server.security.http.YarnAuthenticationFilterInitializer;
+import org.apache.hadoop.yarn.server.timeline.security.TimelineAuthenticationFilterInitializer;
import org.apache.hadoop.yarn.server.webproxy.AppReportFetcher;
import org.apache.hadoop.yarn.server.webproxy.ProxyUriUtils;
import org.apache.hadoop.yarn.server.webproxy.WebAppProxy;
@@ -789,6 +796,43 @@ public void handle(RMNodeEvent event) {
}
protected void startWepApp() {
+
+ // Use the customized yarn filter instead of the standard kerberos filter to
+ // allow users to authenticate using delegation tokens
+ // 3 conditions need to be satisfied -
+ // 1. security is enabled
+ // 2. http auth type is set to kerberos
+ // 3. "yarn.resourcemanager.webapp.use-yarn-filter" override is set to true
+
+ Configuration conf = getConfig();
+ boolean useYarnAuthenticationFilter =
+ conf.getBoolean(
+ YarnConfiguration.RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER,
+ YarnConfiguration.DEFAULT_RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER);
+ String authPrefix = "hadoop.http.authentication.";
+ String authTypeKey = authPrefix + "type";
+ if (UserGroupInformation.isSecurityEnabled()
+ && useYarnAuthenticationFilter
+ && conf.get(authTypeKey, "").equalsIgnoreCase(
+ KerberosAuthenticationHandler.TYPE)) {
+ LOG.info("Using YARN authentication filter(kerberos/delegation-token)"
+ + " for RM webapp authentication");
+ RMAuthenticationHandler
+ .setSecretManager(getClientRMService().rmDTSecretManager);
+ String yarnAuthKey =
+ authPrefix + YarnAuthenticationFilter.AUTH_HANDLER_PROPERTY;
+ conf.setStrings(yarnAuthKey, RMAuthenticationHandler.class.getName());
+ String initializers = conf.get("hadoop.http.filter.initializers");
+ initializers =
+ initializers == null || initializers.length() == 0 ? "" : ","
+ + initializers;
+ if (!initializers.contains(YarnAuthenticationFilterInitializer.class
+ .getName())) {
+ conf.set("hadoop.http.filter.initializers",
+ YarnAuthenticationFilterInitializer.class.getName() + initializers);
+ }
+ }
+
Builder builder =
WebApps
.$for("cluster", ApplicationMasterService.class, masterService,
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/security/RMAuthenticationHandler.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/security/RMAuthenticationHandler.java
new file mode 100644
index 0000000..7a98fb6
--- /dev/null
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/security/RMAuthenticationHandler.java
@@ -0,0 +1,138 @@
+package org.apache.hadoop.yarn.server.resourcemanager.security;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authentication.client.AuthenticationException;
+import org.apache.hadoop.security.authentication.server.AuthenticationToken;
+import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
+import org.apache.hadoop.security.token.Token;
+import org.apache.hadoop.yarn.security.client.RMDelegationTokenIdentifier;
+
+public class RMAuthenticationHandler extends KerberosAuthenticationHandler {
+
+ public static final String TYPE = "kerberos-dt";
+ public static final String HEADER = "Hadoop-YARN-Auth-Delegation-Token";
+
+ static RMDelegationTokenSecretManager secretManager;
+ static boolean secretManagerInitialized = false;
+
+ public RMAuthenticationHandler() {
+ super();
+ }
+
+ /**
+ * Returns authentication type of the handler.
+ *
+ * @return kerberos-dt
+ */
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public boolean managementOperation(AuthenticationToken token,
+ HttpServletRequest request, HttpServletResponse response) {
+ return true;
+ }
+
+ /**
+ * Authenticates a request looking for the delegation header and
+ * verifying it is a valid token. If the header is missing, it delegates the
+ * authentication to the {@link KerberosAuthenticationHandler} unless it is
+ * disabled.
+ *
+ * @param request
+ * the HTTP client request.
+ * @param response
+ * the HTTP client response.
+ *
+ * @return the authentication token for the authenticated request.
+ * @throws IOException
+ * thrown if an IO error occurred.
+ * @throws AuthenticationException
+ * thrown if the authentication failed.
+ */
+ @Override
+ public AuthenticationToken authenticate(HttpServletRequest request,
+ HttpServletResponse response) throws IOException, AuthenticationException {
+
+ AuthenticationToken token;
+ String delegationParam = this.getEncodedDelegationTokenFromRequest(request);
+ if (delegationParam != null) {
+ Token dt =
+ new Token();
+ ;
+ dt.decodeFromUrlString(delegationParam);
+ UserGroupInformation ugi = this.verifyToken(dt);
+ if (ugi == null) {
+ throw new AuthenticationException("Invalid token");
+ }
+ final String shortName = ugi.getShortUserName();
+ token = new AuthenticationToken(shortName, ugi.getUserName(), getType());
+ } else {
+ token = super.authenticate(request, response);
+ if (token != null) {
+ // create a token with auth type set correctly
+ token =
+ new AuthenticationToken(token.getUserName(), token.getName(),
+ super.getType());
+ }
+ }
+ return token;
+ }
+
+ /**
+ * Verifies a delegation token.
+ *
+ * @param token
+ * delegation token to verify.
+ * @return the UGI for the token; null if the verification fails
+ * @throws IOException
+ * thrown if the token could not be verified.
+ */
+ protected UserGroupInformation verifyToken(
+ Token token) throws IOException {
+ if (secretManagerInitialized == false) {
+ throw new IllegalStateException("Secret manager not initialized");
+ }
+ ByteArrayInputStream buf = new ByteArrayInputStream(token.getIdentifier());
+ DataInputStream dis = new DataInputStream(buf);
+ RMDelegationTokenIdentifier id = secretManager.createIdentifier();
+ try {
+ id.readFields(dis);
+ secretManager.verifyToken(id, token.getPassword());
+ } catch (Throwable t) {
+ return null;
+ } finally {
+ dis.close();
+ }
+ return id.getUser();
+ }
+
+ /**
+ * Extract encoded delegation token from request
+ *
+ * @param HTTP
+ * request
+ * @return String containing the encoded token; null if encoded token not
+ * found
+ *
+ */
+ protected String getEncodedDelegationTokenFromRequest(HttpServletRequest req) {
+ String header = req.getHeader(HEADER);
+ return header;
+ }
+
+ public static void setSecretManager(RMDelegationTokenSecretManager manager) {
+ secretManager = manager;
+ secretManagerInitialized = true;
+ }
+
+}
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesDelegationTokenAuthentication.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesDelegationTokenAuthentication.java
new file mode 100644
index 0000000..86ec5e5
--- /dev/null
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServicesDelegationTokenAuthentication.java
@@ -0,0 +1,350 @@
+/**
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.concurrent.Callable;
+
+import javax.ws.rs.core.MediaType;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
+import org.apache.hadoop.minikdc.MiniKdc;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.hadoop.security.authentication.KerberosTestUtils;
+import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
+import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
+import org.apache.hadoop.yarn.conf.YarnConfiguration;
+import org.apache.hadoop.yarn.server.resourcemanager.MockRM;
+import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler;
+import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler;
+import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.ApplicationSubmissionContextInfo;
+import org.apache.hadoop.yarn.util.ConverterUtils;
+import org.codehaus.jettison.json.JSONObject;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.sun.jersey.api.client.ClientResponse.Status;
+
+public class TestRMWebServicesDelegationTokenAuthentication {
+
+ private static final File testRootDir = new File("target",
+ TestRMWebServicesDelegationTokenAuthentication.class.getName() + "-root");
+ private static File httpSpnegoKeytabFile = new File(
+ KerberosTestUtils.getKeytabFile());
+
+ private static String httpSpnegoPrincipal = KerberosTestUtils
+ .getServerPrincipal();
+
+ private static boolean miniKDCStarted = false;
+ private static MiniKdc testMiniKDC;
+ private static MockRM rm;
+
+ // use published header name
+ final static String DelegationTokenHeader =
+ "Hadoop-YARN-Auth-Delegation-Token";
+
+ @BeforeClass
+ public static void setUp() {
+ try {
+ testMiniKDC = new MiniKdc(MiniKdc.createConf(), testRootDir);
+ setupKDC();
+ setupAndStartRM();
+ } catch (Exception e) {
+ assertTrue("Couldn't create MiniKDC", false);
+ }
+ }
+
+ @AfterClass
+ public static void tearDown() {
+ testMiniKDC.stop();
+ rm.stop();
+ }
+
+ public TestRMWebServicesDelegationTokenAuthentication() throws Exception {
+ super();
+ }
+
+ private static void setupAndStartRM() throws Exception {
+ Configuration rmconf = new Configuration();
+ rmconf.setInt(YarnConfiguration.RM_AM_MAX_ATTEMPTS,
+ YarnConfiguration.DEFAULT_RM_AM_MAX_ATTEMPTS);
+ rmconf.setClass(YarnConfiguration.RM_SCHEDULER, FifoScheduler.class,
+ ResourceScheduler.class);
+ rmconf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true);
+ String httpPrefix = "hadoop.http.authentication.";
+ rmconf.setStrings(httpPrefix + "type", "kerberos");
+ rmconf.set(httpPrefix + KerberosAuthenticationHandler.PRINCIPAL,
+ httpSpnegoPrincipal);
+ rmconf.set(httpPrefix + KerberosAuthenticationHandler.KEYTAB,
+ httpSpnegoKeytabFile.getAbsolutePath());
+ // use any file for signature secret
+ rmconf.set(httpPrefix + AuthenticationFilter.SIGNATURE_SECRET + ".file",
+ httpSpnegoKeytabFile.getAbsolutePath());
+ rmconf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION,
+ "kerberos");
+ rmconf.setBoolean(YarnConfiguration.RM_WEBAPP_DELEGATION_TOKEN_AUTH_FILTER,
+ true);
+ rmconf.set(YarnConfiguration.RM_WEBAPP_SPNEGO_USER_NAME_KEY,
+ httpSpnegoPrincipal);
+ rmconf.set(YarnConfiguration.RM_KEYTAB,
+ httpSpnegoKeytabFile.getAbsolutePath());
+ rmconf.set(YarnConfiguration.RM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY,
+ httpSpnegoKeytabFile.getAbsolutePath());
+ rmconf.set(YarnConfiguration.NM_WEBAPP_SPNEGO_USER_NAME_KEY,
+ httpSpnegoPrincipal);
+ rmconf.set(YarnConfiguration.NM_WEBAPP_SPNEGO_KEYTAB_FILE_KEY,
+ httpSpnegoKeytabFile.getAbsolutePath());
+ rmconf.setBoolean("mockrm.webapp.enabled", true);
+ UserGroupInformation.setConfiguration(rmconf);
+ rm = new MockRM(rmconf);
+ rm.start();
+
+ }
+
+ private static void setupKDC() throws Exception {
+ if (miniKDCStarted == false) {
+ testMiniKDC.start();
+ getKdc().createPrincipal(httpSpnegoKeytabFile, "HTTP/localhost",
+ "client", UserGroupInformation.getLoginUser().getShortUserName());
+ miniKDCStarted = true;
+ }
+ }
+
+ private static MiniKdc getKdc() {
+ return testMiniKDC;
+ }
+
+ // Test that you can authenticate with only delegation tokens
+ // 1. Get a delegation token using Kerberos auth(this ends up
+ // testing the fallback authenticator)
+ // 2. Submit an app without kerberos or delegation-token
+ // - we should get an UNAUTHORIZED response
+ // 3. Submit same app with delegation-token
+ // - we should get OK response
+ // - confirm owner of the app is the user whose
+ // delegation-token we used
+
+ @Test
+ public void testDelegationTokenAuth() throws Exception {
+ final String token = getDelegationToken("test");
+
+ ApplicationSubmissionContextInfo app =
+ new ApplicationSubmissionContextInfo();
+ String appid = "application_123_0";
+ app.setApplicationId(appid);
+ String requestBody = getMarshalledAppInfo(app);
+
+ URL url = new URL("http://localhost:8088/ws/v1/cluster/apps");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ setupConn(conn, "POST", "application/xml", requestBody);
+
+ // this should fail with unauthorized because only
+ // auth is kerberos or delegation token
+ try {
+ conn.getInputStream();
+ fail("we should not be here");
+ } catch (IOException e) {
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), conn.getResponseCode());
+ }
+
+ conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty(DelegationTokenHeader, token);
+ setupConn(conn, "POST", MediaType.APPLICATION_XML, requestBody);
+
+ // this should not fail
+ conn.getInputStream();
+ boolean appExists =
+ rm.getRMContext().getRMApps()
+ .containsKey(ConverterUtils.toApplicationId(appid));
+ assertTrue(appExists);
+ RMApp actualApp =
+ rm.getRMContext().getRMApps()
+ .get(ConverterUtils.toApplicationId(appid));
+ String owner = actualApp.getUser();
+ assertEquals("client", owner);
+
+ return;
+ }
+
+ // Test to make sure that cancelled delegation tokens
+ // are rejected
+ @Test
+ public void testCancelledDelegationToken() throws Exception {
+ String token = getDelegationToken("client");
+ cancelDelegationToken(token);
+ ApplicationSubmissionContextInfo app =
+ new ApplicationSubmissionContextInfo();
+ String appid = "application_123_0";
+ app.setApplicationId(appid);
+ String requestBody = getMarshalledAppInfo(app);
+
+ URL url = new URL("http://localhost:8088/ws/v1/cluster/apps");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty(DelegationTokenHeader, token);
+ setupConn(conn, "POST", MediaType.APPLICATION_XML, requestBody);
+
+ // this should fail with unauthorized because only
+ // auth is kerberos or delegation token
+ try {
+ conn.getInputStream();
+ fail("Authentication should fail with expired delegation tokens");
+ } catch (IOException e) {
+ assertEquals(Status.FORBIDDEN.getStatusCode(), conn.getResponseCode());
+ }
+ return;
+ }
+
+ // Test to make sure that we can't do delegation token
+ // functions using just delegation token auth
+ @Test
+ public void testDelegationTokenOps() throws Exception {
+ String token = getDelegationToken("client");
+ String createRequest = "{\"renewer\":\"test\"}";
+ String renewRequest = "{\"token\": \"" + token + "\"}";
+
+ // first test create and renew
+ String[] requests = { createRequest, renewRequest };
+ for (String requestBody : requests) {
+ URL url = new URL("http://localhost:8088/ws/v1/cluster/delegation-token");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty(DelegationTokenHeader, token);
+ setupConn(conn, "POST", MediaType.APPLICATION_JSON, requestBody);
+ try {
+ conn.getInputStream();
+ fail("Creation/Renewing delegation tokens should not be "
+ + "allowed with token auth");
+ } catch (IOException e) {
+ assertEquals(Status.FORBIDDEN.getStatusCode(), conn.getResponseCode());
+ }
+ }
+
+ // test cancel
+ URL url = new URL("http://localhost:8088/ws/v1/cluster/delegation-token");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty(DelegationTokenHeader, token);
+ conn.setRequestProperty(RMWebServices.DELEGATION_TOKEN_HEADER, token);
+ setupConn(conn, "DELETE", null, null);
+ try {
+ conn.getInputStream();
+ fail("Cancelling delegation tokens should not be allowed with token auth");
+ } catch (IOException e) {
+ assertEquals(Status.FORBIDDEN.getStatusCode(), conn.getResponseCode());
+ }
+ return;
+ }
+
+ private String getDelegationToken(final String renewer) throws Exception {
+ String token = KerberosTestUtils.doAsClient(new Callable() {
+ @Override
+ public String call() throws Exception {
+ String ret = null;
+ String body = "{\"renewer\":\"" + renewer + "\"}";
+ URL url =
+ new URL("http://localhost:8088/ws/v1/cluster/delegation-token");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ setupConn(conn, "POST", MediaType.APPLICATION_JSON, body);
+ InputStream response = conn.getInputStream();
+ assertEquals(Status.OK.getStatusCode(), conn.getResponseCode());
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new InputStreamReader(response, "UTF8"));
+ for (String line; (line = reader.readLine()) != null;) {
+ JSONObject obj = new JSONObject(line);
+ if (obj.has("token")) {
+ reader.close();
+ response.close();
+ ret = obj.getString("token");
+ break;
+ }
+ }
+ } finally {
+ IOUtils.closeQuietly(reader);
+ IOUtils.closeQuietly(response);
+ }
+ return ret;
+ }
+ });
+ return token;
+ }
+
+ private void cancelDelegationToken(final String tokenString) throws Exception {
+
+ KerberosTestUtils.doAsClient(new Callable() {
+ @Override
+ public Void call() throws Exception {
+ URL url =
+ new URL("http://localhost:8088/ws/v1/cluster/delegation-token");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setRequestProperty(RMWebServices.DELEGATION_TOKEN_HEADER,
+ tokenString);
+ setupConn(conn, "DELETE", null, null);
+ InputStream response = conn.getInputStream();
+ assertEquals(Status.OK.getStatusCode(), conn.getResponseCode());
+ response.close();
+ return null;
+ }
+ });
+ return;
+ }
+
+ private String getMarshalledAppInfo(ApplicationSubmissionContextInfo appInfo)
+ throws Exception {
+
+ StringWriter writer = new StringWriter();
+ JAXBContext context =
+ JAXBContext.newInstance(ApplicationSubmissionContextInfo.class);
+ Marshaller m = context.createMarshaller();
+ m.marshal(appInfo, writer);
+ return writer.toString();
+ }
+
+ private void setupConn(HttpURLConnection conn, String method,
+ String contentType, String body) throws Exception {
+ conn.setRequestMethod(method);
+ conn.setDoOutput(true);
+ conn.setRequestProperty("Accept-Charset", "UTF8");
+ if (contentType != null && !contentType.isEmpty()) {
+ conn.setRequestProperty("Content-Type", contentType + ";charset=UTF8");
+ if (body != null && !body.isEmpty()) {
+ OutputStream stream = conn.getOutputStream();
+ stream.write(body.getBytes("UTF8"));
+ stream.close();
+ }
+ }
+ }
+
+}
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/ResourceManagerRest.apt.vm hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/ResourceManagerRest.apt.vm
index 6359e2b..9e2cfeb 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/ResourceManagerRest.apt.vm
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/apt/ResourceManagerRest.apt.vm
@@ -2927,3 +2927,24 @@ Accept: application/xml
+---+
No response body.
+
+** Authentication using delegation tokens
+
+ This feature is in the alpha mode and may change in the future.
+
+ You can use delegation tokens to authenticate yourself when using YARN RM webservices. However, this requires setting the right configurations. The conditions for this are:
+
+ * Hadoop is setup in secure mode with the authentication type set to kerberos.
+
+ * Hadoop HTTP authentication is setup with the authentication type set to kerberos
+
+ Once setup, delegation tokens can be fetched using the web services listed above and used as shown in an example below:
+
++---+
+ PUT http:///ws/v1/cluster/apps/application_1399397633663_0003/state
+ Hadoop-YARN-Auth-Delegation-Token: MgASY2xpZW50QEVYQU1QTEUuQ09NDHRlc3QtcmVuZXdlcgCKAUbjqcHHigFHB7ZFxwQCFKWD3znCkDSy6SQIjRCLDydxbxvgE1JNX0RFTEVHQVRJT05fVE9LRU4A
+ Content-Type: application/json; charset=UTF8
+ {
+ "state":"KILLED"
+ }
++---+