diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/client/ApiServiceClient.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/client/ApiServiceClient.java index 9232fc8..9153ea6 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/client/ApiServiceClient.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-services/hadoop-yarn-services-api/src/main/java/org/apache/hadoop/yarn/service/client/ApiServiceClient.java @@ -20,6 +20,9 @@ import java.io.File; import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.PrivilegedExceptionAction; import java.text.MessageFormat; import java.util.List; import java.util.Map; @@ -27,12 +30,16 @@ import javax.ws.rs.core.MediaType; import com.google.common.base.Preconditions; + +import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.security.authentication.client.AuthenticatedURL; +import org.apache.hadoop.security.authentication.client.AuthenticationException; +import org.apache.hadoop.security.authentication.util.KerberosUtil; import org.apache.hadoop.yarn.api.ApplicationConstants; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ApplicationReport; @@ -51,8 +58,14 @@ import org.apache.hadoop.yarn.service.utils.JsonSerDeser; import org.apache.hadoop.yarn.service.utils.ServiceApiUtil; import org.apache.hadoop.yarn.util.RMHAUtils; +import org.apache.http.HttpHeaders; import org.codehaus.jackson.map.PropertyNamingStrategy; import org.eclipse.jetty.util.UrlEncoded; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +import org.ietf.jgss.Oid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,6 +84,7 @@ public class ApiServiceClient extends AppAdminClient { private static final Logger LOG = LoggerFactory.getLogger(ApiServiceClient.class); + private static final Base64 base64codec = new Base64(0); protected YarnClient yarnClient; @Override protected void serviceInit(Configuration configuration) @@ -81,6 +95,46 @@ } /** + * Generate SPNEGO challenge request token. + * + * @param server - hostname to contact + * @throws IOException + * @throws InterruptedException + */ + private String generateToken(String server) throws IOException, InterruptedException { + UserGroupInformation currentUser = UserGroupInformation.getCurrentUser(); + LOG.debug("The user credential is {}", currentUser); + String challenge = currentUser.doAs(new PrivilegedExceptionAction() { + @Override public String run() throws Exception { + try { + // This Oid for Kerberos GSS-API mechanism. + Oid mechOid = KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID"); + GSSManager manager = GSSManager.getInstance(); + // GSS name for server + GSSName serverName = manager.createName("HTTP@" + server, GSSName.NT_HOSTBASED_SERVICE); + // Create a GSSContext for authentication with the service. + // We're passing client credentials as null since we want them to be read from the Subject. + GSSContext gssContext = + manager.createContext(serverName.canonicalize(mechOid), mechOid, null, GSSContext.DEFAULT_LIFETIME); + gssContext.requestMutualAuth(true); + gssContext.requestCredDeleg(true); + // Establish context + byte[] inToken = new byte[0]; + byte[] outToken = gssContext.initSecContext(inToken, 0, inToken.length); + gssContext.dispose(); + // Base64 encoded and stringified token for server + LOG.debug("Got valid challenge for host {}", serverName); + return new String(base64codec.encode(outToken), StandardCharsets.US_ASCII); + } + catch (GSSException | IllegalAccessException | NoSuchFieldException | ClassNotFoundException e) { + LOG.error("Error: {}", e); + throw new AuthenticationException(e); + } + } + }); + return challenge; + } + /** * Calculate Resource Manager address base on working REST API. */ private String getRMWebAddress() { @@ -116,8 +170,10 @@ private String getRMWebAddress() { WebResource webResource = client .resource(sb.toString()); if (useKerberos) { - AuthenticatedURL.Token token = new AuthenticatedURL.Token(); - webResource.header("WWW-Authenticate", token); + String[] server = host.split(":"); + String challenge = generateToken(server[0]); + webResource.header(HttpHeaders.AUTHORIZATION, "Negotiate " + challenge); + LOG.debug("Authorization: Negotiate {}", challenge); } ClientResponse test = webResource.get(ClientResponse.class); if (test.getStatus() == 200) { @@ -206,8 +262,13 @@ private Builder getApiClient(String requestPath) Builder builder = client .resource(requestPath).type(MediaType.APPLICATION_JSON); if (conf.get("hadoop.http.authentication.type").equals("kerberos")) { - AuthenticatedURL.Token token = new AuthenticatedURL.Token(); - builder.header("WWW-Authenticate", token); + try { + URI url = new URI(requestPath); + String challenge = generateToken(url.getHost()); + builder.header(HttpHeaders.AUTHORIZATION, "Negotiate " + challenge); + } catch (Exception e) { + throw new IOException(e); + } } return builder .accept("application/json;charset=utf-8");