HADOOP-10158. SPNEGO should work with multiple interfaces/SPNs. Contributed by Daryn Sharp.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1593362 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Kihwal Lee 2014-05-08 18:26:58 +00:00
parent 584e384fd6
commit 47f03bc9fe
4 changed files with 129 additions and 39 deletions

View File

@ -34,16 +34,18 @@
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedActionException; import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction; import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern;
import static org.apache.hadoop.util.PlatformName.IBM_JAVA; import static org.apache.hadoop.util.PlatformName.IBM_JAVA;
@ -140,10 +142,10 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
*/ */
public static final String NAME_RULES = TYPE + ".name.rules"; public static final String NAME_RULES = TYPE + ".name.rules";
private String principal;
private String keytab; private String keytab;
private GSSManager gssManager; private GSSManager gssManager;
private LoginContext loginContext; private Subject serverSubject = new Subject();
private List<LoginContext> loginContexts = new ArrayList<LoginContext>();
/** /**
* Initializes the authentication handler instance. * Initializes the authentication handler instance.
@ -159,7 +161,7 @@ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
@Override @Override
public void init(Properties config) throws ServletException { public void init(Properties config) throws ServletException {
try { try {
principal = config.getProperty(PRINCIPAL, principal); String principal = config.getProperty(PRINCIPAL);
if (principal == null || principal.trim().length() == 0) { if (principal == null || principal.trim().length() == 0) {
throw new ServletException("Principal not defined in configuration"); throw new ServletException("Principal not defined in configuration");
} }
@ -170,23 +172,40 @@ public void init(Properties config) throws ServletException {
if (!new File(keytab).exists()) { if (!new File(keytab).exists()) {
throw new ServletException("Keytab does not exist: " + keytab); throw new ServletException("Keytab does not exist: " + keytab);
} }
// use all SPNEGO principals in the keytab if a principal isn't
// specifically configured
final String[] spnegoPrincipals;
if (principal.equals("*")) {
spnegoPrincipals = KerberosUtil.getPrincipalNames(
keytab, Pattern.compile("HTTP/.*"));
if (spnegoPrincipals.length == 0) {
throw new ServletException("Principals do not exist in the keytab");
}
} else {
spnegoPrincipals = new String[]{principal};
}
String nameRules = config.getProperty(NAME_RULES, null); String nameRules = config.getProperty(NAME_RULES, null);
if (nameRules != null) { if (nameRules != null) {
KerberosName.setRules(nameRules); KerberosName.setRules(nameRules);
} }
Set<Principal> principals = new HashSet<Principal>(); for (String spnegoPrincipal : spnegoPrincipals) {
principals.add(new KerberosPrincipal(principal)); LOG.info("Login using keytab {}, for principal {}",
Subject subject = new Subject(false, principals, new HashSet<Object>(), new HashSet<Object>()); keytab, principal);
final KerberosConfiguration kerberosConfiguration =
KerberosConfiguration kerberosConfiguration = new KerberosConfiguration(keytab, principal); new KerberosConfiguration(keytab, spnegoPrincipal);
final LoginContext loginContext =
LOG.info("Login using keytab "+keytab+", for principal "+principal); new LoginContext("", serverSubject, null, kerberosConfiguration);
loginContext = new LoginContext("", subject, null, kerberosConfiguration); try {
loginContext.login(); loginContext.login();
} catch (LoginException le) {
Subject serverSubject = loginContext.getSubject(); LOG.warn("Failed to login as [{}]", spnegoPrincipal, le);
throw new AuthenticationException(le);
}
loginContexts.add(loginContext);
}
try { try {
gssManager = Subject.doAs(serverSubject, new PrivilegedExceptionAction<GSSManager>() { gssManager = Subject.doAs(serverSubject, new PrivilegedExceptionAction<GSSManager>() {
@ -198,7 +217,6 @@ public GSSManager run() throws Exception {
} catch (PrivilegedActionException ex) { } catch (PrivilegedActionException ex) {
throw ex.getException(); throw ex.getException();
} }
LOG.info("Initialized, principal [{}] from keytab [{}]", principal, keytab);
} catch (Exception ex) { } catch (Exception ex) {
throw new ServletException(ex); throw new ServletException(ex);
} }
@ -211,14 +229,16 @@ public GSSManager run() throws Exception {
*/ */
@Override @Override
public void destroy() { public void destroy() {
try { keytab = null;
if (loginContext != null) { serverSubject = null;
for (LoginContext loginContext : loginContexts) {
try {
loginContext.logout(); loginContext.logout();
loginContext = null; } catch (LoginException ex) {
LOG.warn(ex.getMessage(), ex);
} }
} catch (LoginException ex) {
LOG.warn(ex.getMessage(), ex);
} }
loginContexts.clear();
} }
/** /**
@ -233,12 +253,12 @@ public String getType() {
} }
/** /**
* Returns the Kerberos principal used by the authentication handler. * Returns the Kerberos principals used by the authentication handler.
* *
* @return the Kerberos principal used by the authentication handler. * @return the Kerberos principals used by the authentication handler.
*/ */
protected String getPrincipal() { protected Set<KerberosPrincipal> getPrincipals() {
return principal; return serverSubject.getPrincipals(KerberosPrincipal.class);
} }
/** /**
@ -304,7 +324,7 @@ public AuthenticationToken authenticate(HttpServletRequest request, final HttpSe
authorization = authorization.substring(KerberosAuthenticator.NEGOTIATE.length()).trim(); authorization = authorization.substring(KerberosAuthenticator.NEGOTIATE.length()).trim();
final Base64 base64 = new Base64(0); final Base64 base64 = new Base64(0);
final byte[] clientToken = base64.decode(authorization); final byte[] clientToken = base64.decode(authorization);
Subject serverSubject = loginContext.getSubject(); final String serverName = request.getServerName();
try { try {
token = Subject.doAs(serverSubject, new PrivilegedExceptionAction<AuthenticationToken>() { token = Subject.doAs(serverSubject, new PrivilegedExceptionAction<AuthenticationToken>() {
@ -314,15 +334,15 @@ public AuthenticationToken run() throws Exception {
GSSContext gssContext = null; GSSContext gssContext = null;
GSSCredential gssCreds = null; GSSCredential gssCreds = null;
try { try {
if (IBM_JAVA) { gssCreds = gssManager.createCredential(
// IBM JDK needs non-null credentials to be passed to createContext here, with gssManager.createName(
// SPNEGO mechanism specified, otherwise JGSS will use its default mechanism KerberosUtil.getServicePrincipal("HTTP", serverName),
// only, which is Kerberos V5. KerberosUtil.getOidInstance("NT_GSS_KRB5_PRINCIPAL")),
gssCreds = gssManager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME, GSSCredential.INDEFINITE_LIFETIME,
new Oid[]{KerberosUtil.getOidInstance("GSS_SPNEGO_MECH_OID"), new Oid[]{
KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID")}, KerberosUtil.getOidInstance("GSS_SPNEGO_MECH_OID"),
GSSCredential.ACCEPT_ONLY); KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID")},
} GSSCredential.ACCEPT_ONLY);
gssContext = gssManager.createContext(gssCreds); gssContext = gssManager.createContext(gssCreds);
byte[] serverToken = gssContext.acceptSecContext(clientToken, 0, clientToken.length); byte[] serverToken = gssContext.acceptSecContext(clientToken, 0, clientToken.length);
if (serverToken != null && serverToken.length > 0) { if (serverToken != null && serverToken.length > 0) {

View File

@ -18,6 +18,7 @@
import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.client.KerberosAuthenticator; import org.apache.hadoop.security.authentication.client.KerberosAuthenticator;
import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.security.authentication.util.KerberosName; import org.apache.hadoop.security.authentication.util.KerberosName;
import org.apache.hadoop.security.authentication.util.KerberosUtil; import org.apache.hadoop.security.authentication.util.KerberosUtil;
import org.ietf.jgss.GSSContext; import org.ietf.jgss.GSSContext;
@ -30,10 +31,18 @@
import org.mockito.Mockito; import org.mockito.Mockito;
import org.ietf.jgss.Oid; import org.ietf.jgss.Oid;
import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.File; import java.io.File;
import java.security.Principal;
import java.util.Arrays;
import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
public class TestKerberosAuthenticationHandler public class TestKerberosAuthenticationHandler
@ -110,8 +119,65 @@ public void testNameRules() throws Exception {
@Test(timeout=60000) @Test(timeout=60000)
public void testInit() throws Exception { public void testInit() throws Exception {
Assert.assertEquals(KerberosTestUtils.getServerPrincipal(), handler.getPrincipal());
Assert.assertEquals(KerberosTestUtils.getKeytabFile(), handler.getKeytab()); Assert.assertEquals(KerberosTestUtils.getKeytabFile(), handler.getKeytab());
Set<KerberosPrincipal> principals = handler.getPrincipals();
Principal expectedPrincipal =
new KerberosPrincipal(KerberosTestUtils.getServerPrincipal());
Assert.assertTrue(principals.contains(expectedPrincipal));
Assert.assertEquals(1, principals.size());
}
// dynamic configuration of HTTP principals
@Test(timeout=60000)
public void testDynamicPrincipalDiscovery() throws Exception {
String[] keytabUsers = new String[]{
"HTTP/host1", "HTTP/host2", "HTTP2/host1", "XHTTP/host"
};
String keytab = KerberosTestUtils.getKeytabFile();
getKdc().createPrincipal(new File(keytab), keytabUsers);
// destroy handler created in setUp()
handler.destroy();
Properties props = new Properties();
props.setProperty(KerberosAuthenticationHandler.KEYTAB, keytab);
props.setProperty(KerberosAuthenticationHandler.PRINCIPAL, "*");
handler = getNewAuthenticationHandler();
handler.init(props);
Assert.assertEquals(KerberosTestUtils.getKeytabFile(), handler.getKeytab());
Set<KerberosPrincipal> loginPrincipals = handler.getPrincipals();
for (String user : keytabUsers) {
Principal principal = new KerberosPrincipal(
user + "@" + KerberosTestUtils.getRealm());
boolean expected = user.startsWith("HTTP/");
Assert.assertEquals("checking for "+user, expected,
loginPrincipals.contains(principal));
}
}
// dynamic configuration of HTTP principals
@Test(timeout=60000)
public void testDynamicPrincipalDiscoveryMissingPrincipals() throws Exception {
String[] keytabUsers = new String[]{"hdfs/localhost"};
String keytab = KerberosTestUtils.getKeytabFile();
getKdc().createPrincipal(new File(keytab), keytabUsers);
// destroy handler created in setUp()
handler.destroy();
Properties props = new Properties();
props.setProperty(KerberosAuthenticationHandler.KEYTAB, keytab);
props.setProperty(KerberosAuthenticationHandler.PRINCIPAL, "*");
handler = getNewAuthenticationHandler();
try {
handler.init(props);
Assert.fail("init should have failed");
} catch (ServletException ex) {
Assert.assertEquals("Principals do not exist in the keytab",
ex.getCause().getMessage());
} catch (Throwable t) {
Assert.fail("wrong exception: "+t);
}
} }
@Test(timeout=60000) @Test(timeout=60000)
@ -190,7 +256,8 @@ public String call() throws Exception {
Mockito.when(request.getHeader(KerberosAuthenticator.AUTHORIZATION)) Mockito.when(request.getHeader(KerberosAuthenticator.AUTHORIZATION))
.thenReturn(KerberosAuthenticator.NEGOTIATE + " " + token); .thenReturn(KerberosAuthenticator.NEGOTIATE + " " + token);
Mockito.when(request.getServerName()).thenReturn("localhost");
AuthenticationToken authToken = handler.authenticate(request, response); AuthenticationToken authToken = handler.authenticate(request, response);
if (authToken != null) { if (authToken != null) {

View File

@ -377,6 +377,9 @@ Release 2.5.0 - UNRELEASED
HADOOP-10467. Enable proxyuser specification to support list of users in HADOOP-10467. Enable proxyuser specification to support list of users in
addition to list of groups (Benoy Antony via Arpit Agarwal) addition to list of groups (Benoy Antony via Arpit Agarwal)
HADOOP-10158. SPNEGO should work with multiple interfaces/SPNs.
(daryn via kihwal)
OPTIMIZATIONS OPTIMIZATIONS
BUG FIXES BUG FIXES

View File

@ -146,7 +146,7 @@ WebHDFS REST API
*-------------------------------------------------+---------------------------------------------------+ *-------------------------------------------------+---------------------------------------------------+
| <<<dfs.webhdfs.enabled >>> | Enable/disable WebHDFS in Namenodes and Datanodes | | <<<dfs.webhdfs.enabled >>> | Enable/disable WebHDFS in Namenodes and Datanodes |
*-------------------------------------------------+---------------------------------------------------+ *-------------------------------------------------+---------------------------------------------------+
| <<<dfs.web.authentication.kerberos.principal>>> | The HTTP Kerberos principal used by Hadoop-Auth in the HTTP endpoint. The HTTP Kerberos principal MUST start with 'HTTP/' per Kerberos HTTP SPNEGO specification. | | <<<dfs.web.authentication.kerberos.principal>>> | The HTTP Kerberos principal used by Hadoop-Auth in the HTTP endpoint. The HTTP Kerberos principal MUST start with 'HTTP/' per Kerberos HTTP SPNEGO specification. A value of "*" will use all HTTP principals found in the keytab. |
*-------------------------------------------------+---------------------------------------------------+ *-------------------------------------------------+---------------------------------------------------+
| <<<dfs.web.authentication.kerberos.keytab >>> | The Kerberos keytab file with the credentials for the HTTP Kerberos principal used by Hadoop-Auth in the HTTP endpoint. | | <<<dfs.web.authentication.kerberos.keytab >>> | The Kerberos keytab file with the credentials for the HTTP Kerberos principal used by Hadoop-Auth in the HTTP endpoint. |
*-------------------------------------------------+---------------------------------------------------+ *-------------------------------------------------+---------------------------------------------------+