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:
parent
584e384fd6
commit
47f03bc9fe
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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. |
|
||||||
*-------------------------------------------------+---------------------------------------------------+
|
*-------------------------------------------------+---------------------------------------------------+
|
||||||
|
Loading…
Reference in New Issue
Block a user