Author: kihwal Date: Thu May 8 18:28:22 2014 New Revision: 1593363 URL: http://svn.apache.org/r1593363 Log: svn merge -c 1593362 merging from trunk to branch-2 to fix:HADOOP-10158. SPNEGO should work with multiple interfaces/SPNs. Contributed by Daryn Sharp.
Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-common/CHANGES.txt Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java?rev=1593363&r1=1593362&r2=1593363&view=diff ============================================================================== --- hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java (original) +++ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java Thu May 8 18:28:22 2014 @@ -34,16 +34,18 @@ import javax.security.auth.login.LoginEx import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + import java.io.File; import java.io.IOException; -import java.security.Principal; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; +import java.util.regex.Pattern; import static org.apache.hadoop.util.PlatformName.IBM_JAVA; @@ -140,10 +142,10 @@ public class KerberosAuthenticationHandl */ public static final String NAME_RULES = TYPE + ".name.rules"; - private String principal; private String keytab; private GSSManager gssManager; - private LoginContext loginContext; + private Subject serverSubject = new Subject(); + private List<LoginContext> loginContexts = new ArrayList<LoginContext>(); /** * Initializes the authentication handler instance. @@ -159,7 +161,7 @@ public class KerberosAuthenticationHandl @Override public void init(Properties config) throws ServletException { try { - principal = config.getProperty(PRINCIPAL, principal); + String principal = config.getProperty(PRINCIPAL); if (principal == null || principal.trim().length() == 0) { throw new ServletException("Principal not defined in configuration"); } @@ -170,23 +172,40 @@ public class KerberosAuthenticationHandl if (!new File(keytab).exists()) { 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); if (nameRules != null) { KerberosName.setRules(nameRules); } - Set<Principal> principals = new HashSet<Principal>(); - principals.add(new KerberosPrincipal(principal)); - Subject subject = new Subject(false, principals, new HashSet<Object>(), new HashSet<Object>()); - - KerberosConfiguration kerberosConfiguration = new KerberosConfiguration(keytab, principal); - - LOG.info("Login using keytab "+keytab+", for principal "+principal); - loginContext = new LoginContext("", subject, null, kerberosConfiguration); - loginContext.login(); - - Subject serverSubject = loginContext.getSubject(); + for (String spnegoPrincipal : spnegoPrincipals) { + LOG.info("Login using keytab {}, for principal {}", + keytab, principal); + final KerberosConfiguration kerberosConfiguration = + new KerberosConfiguration(keytab, spnegoPrincipal); + final LoginContext loginContext = + new LoginContext("", serverSubject, null, kerberosConfiguration); + try { + loginContext.login(); + } catch (LoginException le) { + LOG.warn("Failed to login as [{}]", spnegoPrincipal, le); + throw new AuthenticationException(le); + } + loginContexts.add(loginContext); + } try { gssManager = Subject.doAs(serverSubject, new PrivilegedExceptionAction<GSSManager>() { @@ -198,7 +217,6 @@ public class KerberosAuthenticationHandl } catch (PrivilegedActionException ex) { throw ex.getException(); } - LOG.info("Initialized, principal [{}] from keytab [{}]", principal, keytab); } catch (Exception ex) { throw new ServletException(ex); } @@ -211,14 +229,16 @@ public class KerberosAuthenticationHandl */ @Override public void destroy() { - try { - if (loginContext != null) { + keytab = null; + serverSubject = null; + for (LoginContext loginContext : loginContexts) { + try { 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 class KerberosAuthenticationHandl } /** - * 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() { - return principal; + protected Set<KerberosPrincipal> getPrincipals() { + return serverSubject.getPrincipals(KerberosPrincipal.class); } /** @@ -304,7 +324,7 @@ public class KerberosAuthenticationHandl authorization = authorization.substring(KerberosAuthenticator.NEGOTIATE.length()).trim(); final Base64 base64 = new Base64(0); final byte[] clientToken = base64.decode(authorization); - Subject serverSubject = loginContext.getSubject(); + final String serverName = request.getServerName(); try { token = Subject.doAs(serverSubject, new PrivilegedExceptionAction<AuthenticationToken>() { @@ -314,15 +334,15 @@ public class KerberosAuthenticationHandl GSSContext gssContext = null; GSSCredential gssCreds = null; try { - if (IBM_JAVA) { - // IBM JDK needs non-null credentials to be passed to createContext here, with - // SPNEGO mechanism specified, otherwise JGSS will use its default mechanism - // only, which is Kerberos V5. - gssCreds = gssManager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME, - new Oid[]{KerberosUtil.getOidInstance("GSS_SPNEGO_MECH_OID"), - KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID")}, - GSSCredential.ACCEPT_ONLY); - } + gssCreds = gssManager.createCredential( + gssManager.createName( + KerberosUtil.getServicePrincipal("HTTP", serverName), + KerberosUtil.getOidInstance("NT_GSS_KRB5_PRINCIPAL")), + GSSCredential.INDEFINITE_LIFETIME, + new Oid[]{ + KerberosUtil.getOidInstance("GSS_SPNEGO_MECH_OID"), + KerberosUtil.getOidInstance("GSS_KRB5_MECH_OID")}, + GSSCredential.ACCEPT_ONLY); gssContext = gssManager.createContext(gssCreds); byte[] serverToken = gssContext.acceptSecContext(clientToken, 0, clientToken.length); if (serverToken != null && serverToken.length > 0) { Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java?rev=1593363&r1=1593362&r2=1593363&view=diff ============================================================================== --- hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java (original) +++ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestKerberosAuthenticationHandler.java Thu May 8 18:28:22 2014 @@ -18,6 +18,7 @@ import org.apache.hadoop.security.authen import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.authentication.client.KerberosAuthenticator; 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.KerberosUtil; import org.ietf.jgss.GSSContext; @@ -30,10 +31,18 @@ import org.junit.Test; import org.mockito.Mockito; 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.HttpServletResponse; + import java.io.File; +import java.security.Principal; +import java.util.Arrays; +import java.util.List; import java.util.Properties; +import java.util.Set; import java.util.concurrent.Callable; public class TestKerberosAuthenticationHandler @@ -110,8 +119,65 @@ public class TestKerberosAuthenticationH @Test(timeout=60000) public void testInit() throws Exception { - Assert.assertEquals(KerberosTestUtils.getServerPrincipal(), handler.getPrincipal()); 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) @@ -190,7 +256,8 @@ public class TestKerberosAuthenticationH Mockito.when(request.getHeader(KerberosAuthenticator.AUTHORIZATION)) .thenReturn(KerberosAuthenticator.NEGOTIATE + " " + token); - + Mockito.when(request.getServerName()).thenReturn("localhost"); + AuthenticationToken authToken = handler.authenticate(request, response); if (authToken != null) { Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-common/CHANGES.txt URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-2/hadoop-common-project/hadoop-common/CHANGES.txt?rev=1593363&r1=1593362&r2=1593363&view=diff ============================================================================== --- hadoop/common/branches/branch-2/hadoop-common-project/hadoop-common/CHANGES.txt (original) +++ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-common/CHANGES.txt Thu May 8 18:28:22 2014 @@ -45,6 +45,9 @@ Release 2.5.0 - UNRELEASED HADOOP-10467. Enable proxyuser specification to support list of users in addition to list of groups (Benoy Antony via Arpit Agarwal) + HADOOP-10158. SPNEGO should work with multiple interfaces/SPNs. + (daryn via kihwal) + OPTIMIZATIONS BUG FIXES