Return-Path: X-Original-To: apmail-hadoop-common-commits-archive@www.apache.org Delivered-To: apmail-hadoop-common-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 5C3921160E for ; Tue, 5 Aug 2014 21:25:49 +0000 (UTC) Received: (qmail 43151 invoked by uid 500); 5 Aug 2014 21:25:49 -0000 Delivered-To: apmail-hadoop-common-commits-archive@hadoop.apache.org Received: (qmail 43085 invoked by uid 500); 5 Aug 2014 21:25:49 -0000 Mailing-List: contact common-commits-help@hadoop.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: common-dev@hadoop.apache.org Delivered-To: mailing list common-commits@hadoop.apache.org Received: (qmail 43075 invoked by uid 99); 5 Aug 2014 21:25:49 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 05 Aug 2014 21:25:49 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 05 Aug 2014 21:25:43 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 37A0E238918D; Tue, 5 Aug 2014 21:25:23 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1616006 - in /hadoop/common/branches/branch-2/hadoop-common-project: hadoop-auth/ hadoop-auth/dev-support/ hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/ hadoop-auth/src/main/java/org/apache/hadoop/security/aut... Date: Tue, 05 Aug 2014 21:25:22 -0000 To: common-commits@hadoop.apache.org From: tucu@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20140805212523.37A0E238918D@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: tucu Date: Tue Aug 5 21:25:22 2014 New Revision: 1616006 URL: http://svn.apache.org/r1616006 Log: HADOOP-10791. AuthenticationFilter should support externalizing the secret for signing and provide rotation support. (rkanter via tucu) Added: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/dev-support/ - copied from r1616005, hadoop/common/trunk/hadoop-common-project/hadoop-auth/dev-support/ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/RandomSignerSecretProvider.java - copied unchanged from r1616005, hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/RandomSignerSecretProvider.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/RolloverSignerSecretProvider.java - copied unchanged from r1616005, hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/RolloverSignerSecretProvider.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/SignerSecretProvider.java - copied unchanged from r1616005, hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/SignerSecretProvider.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/StringSignerSecretProvider.java - copied unchanged from r1616005, hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/StringSignerSecretProvider.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestRandomSignerSecretProvider.java - copied unchanged from r1616005, hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestRandomSignerSecretProvider.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestRolloverSignerSecretProvider.java - copied unchanged from r1616005, hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestRolloverSignerSecretProvider.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestStringSignerSecretProvider.java - copied unchanged from r1616005, hadoop/common/trunk/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestStringSignerSecretProvider.java Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/pom.xml hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/Signer.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestSigner.java hadoop/common/branches/branch-2/hadoop-common-project/hadoop-common/CHANGES.txt Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/pom.xml URL: http://svn.apache.org/viewvc/hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/pom.xml?rev=1616006&r1=1616005&r2=1616006&view=diff ============================================================================== --- hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/pom.xml (original) +++ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/pom.xml Tue Aug 5 21:25:22 2014 @@ -155,6 +155,13 @@ + + org.codehaus.mojo + findbugs-maven-plugin + + ${basedir}/dev-support/findbugsExcludeFile.xml + + Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.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/AuthenticationFilter.java?rev=1616006&r1=1616005&r2=1616006&view=diff ============================================================================== --- hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java (original) +++ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java Tue Aug 5 21:25:22 2014 @@ -19,6 +19,9 @@ import org.apache.hadoop.security.authen import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.authentication.util.Signer; import org.apache.hadoop.security.authentication.util.SignerException; +import org.apache.hadoop.security.authentication.util.RandomSignerSecretProvider; +import org.apache.hadoop.security.authentication.util.SignerSecretProvider; +import org.apache.hadoop.security.authentication.util.StringSignerSecretProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -107,11 +110,28 @@ public class AuthenticationFilter implem */ public static final String COOKIE_PATH = "cookie.path"; - private static final Random RAN = new Random(); + /** + * Constant for the configuration property that indicates the name of the + * SignerSecretProvider class to use. If not specified, SIGNATURE_SECRET + * will be used or a random secret. + */ + public static final String SIGNER_SECRET_PROVIDER_CLASS = + "signer.secret.provider"; + + /** + * Constant for the attribute that can be used for providing a custom + * object that subclasses the SignerSecretProvider. Note that this should be + * set in the ServletContext and the class should already be initialized. + * If not specified, SIGNER_SECRET_PROVIDER_CLASS will be used. + */ + public static final String SIGNATURE_PROVIDER_ATTRIBUTE = + "org.apache.hadoop.security.authentication.util.SignerSecretProvider"; private Signer signer; + private SignerSecretProvider secretProvider; private AuthenticationHandler authHandler; private boolean randomSecret; + private boolean customSecretProvider; private long validity; private String cookieDomain; private String cookiePath; @@ -159,14 +179,46 @@ public class AuthenticationFilter implem } catch (IllegalAccessException ex) { throw new ServletException(ex); } - String signatureSecret = config.getProperty(configPrefix + SIGNATURE_SECRET); - if (signatureSecret == null) { - signatureSecret = Long.toString(RAN.nextLong()); - randomSecret = true; - LOG.warn("'signature.secret' configuration not set, using a random value as secret"); + + validity = Long.parseLong(config.getProperty(AUTH_TOKEN_VALIDITY, "36000")) + * 1000; //10 hours + secretProvider = (SignerSecretProvider) filterConfig.getServletContext(). + getAttribute(SIGNATURE_PROVIDER_ATTRIBUTE); + if (secretProvider == null) { + String signerSecretProviderClassName = + config.getProperty(configPrefix + SIGNER_SECRET_PROVIDER_CLASS, null); + if (signerSecretProviderClassName == null) { + String signatureSecret = + config.getProperty(configPrefix + SIGNATURE_SECRET, null); + if (signatureSecret != null) { + secretProvider = new StringSignerSecretProvider(signatureSecret); + } else { + secretProvider = new RandomSignerSecretProvider(); + randomSecret = true; + } + } else { + try { + Class klass = Thread.currentThread().getContextClassLoader(). + loadClass(signerSecretProviderClassName); + secretProvider = (SignerSecretProvider) klass.newInstance(); + customSecretProvider = true; + } catch (ClassNotFoundException ex) { + throw new ServletException(ex); + } catch (InstantiationException ex) { + throw new ServletException(ex); + } catch (IllegalAccessException ex) { + throw new ServletException(ex); + } + } + try { + secretProvider.init(config, validity); + } catch (Exception ex) { + throw new ServletException(ex); + } + } else { + customSecretProvider = true; } - signer = new Signer(signatureSecret.getBytes()); - validity = Long.parseLong(config.getProperty(AUTH_TOKEN_VALIDITY, "36000")) * 1000; //10 hours + signer = new Signer(secretProvider); cookieDomain = config.getProperty(COOKIE_DOMAIN, null); cookiePath = config.getProperty(COOKIE_PATH, null); @@ -191,6 +243,15 @@ public class AuthenticationFilter implem } /** + * Returns if a custom implementation of a SignerSecretProvider is being used. + * + * @return if a custom implementation of a SignerSecretProvider is being used. + */ + protected boolean isCustomSignerSecretProvider() { + return customSecretProvider; + } + + /** * Returns the validity time of the generated tokens. * * @return the validity time of the generated tokens, in seconds. @@ -228,6 +289,9 @@ public class AuthenticationFilter implem authHandler.destroy(); authHandler = null; } + if (secretProvider != null) { + secretProvider.destroy(); + } } /** Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/Signer.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/util/Signer.java?rev=1616006&r1=1616005&r2=1616006&view=diff ============================================================================== --- hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/Signer.java (original) +++ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/util/Signer.java Tue Aug 5 21:25:22 2014 @@ -24,18 +24,19 @@ import java.security.NoSuchAlgorithmExce public class Signer { private static final String SIGNATURE = "&s="; - private byte[] secret; + private SignerSecretProvider secretProvider; /** - * Creates a Signer instance using the specified secret. + * Creates a Signer instance using the specified SignerSecretProvider. The + * SignerSecretProvider should already be initialized. * - * @param secret secret to use for creating the digest. + * @param secretProvider The SignerSecretProvider to use */ - public Signer(byte[] secret) { - if (secret == null) { - throw new IllegalArgumentException("secret cannot be NULL"); + public Signer(SignerSecretProvider secretProvider) { + if (secretProvider == null) { + throw new IllegalArgumentException("secretProvider cannot be NULL"); } - this.secret = secret.clone(); + this.secretProvider = secretProvider; } /** @@ -47,11 +48,12 @@ public class Signer { * * @return the signed string. */ - public String sign(String str) { + public synchronized String sign(String str) { if (str == null || str.length() == 0) { throw new IllegalArgumentException("NULL or empty string to sign"); } - String signature = computeSignature(str); + byte[] secret = secretProvider.getCurrentSecret(); + String signature = computeSignature(secret, str); return str + SIGNATURE + signature; } @@ -71,21 +73,19 @@ public class Signer { } String originalSignature = signedStr.substring(index + SIGNATURE.length()); String rawValue = signedStr.substring(0, index); - String currentSignature = computeSignature(rawValue); - if (!originalSignature.equals(currentSignature)) { - throw new SignerException("Invalid signature"); - } + checkSignatures(rawValue, originalSignature); return rawValue; } /** * Returns then signature of a string. * + * @param secret The secret to use * @param str string to sign. * * @return the signature for the string. */ - protected String computeSignature(String str) { + protected String computeSignature(byte[] secret, String str) { try { MessageDigest md = MessageDigest.getInstance("SHA"); md.update(str.getBytes()); @@ -97,4 +97,22 @@ public class Signer { } } + protected void checkSignatures(String rawValue, String originalSignature) + throws SignerException { + boolean isValid = false; + byte[][] secrets = secretProvider.getAllSecrets(); + for (int i = 0; i < secrets.length; i++) { + byte[] secret = secrets[i]; + if (secret != null) { + String currentSignature = computeSignature(secret, rawValue); + if (originalSignature.equals(currentSignature)) { + isValid = true; + break; + } + } + } + if (!isValid) { + throw new SignerException("Invalid signature"); + } + } } Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.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/TestAuthenticationFilter.java?rev=1616006&r1=1616005&r2=1616006&view=diff ============================================================================== --- hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java (original) +++ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java Tue Aug 5 21:25:22 2014 @@ -23,6 +23,7 @@ import java.util.Vector; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; +import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -33,6 +34,8 @@ import javax.servlet.http.HttpServletRes import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.authentication.util.Signer; +import org.apache.hadoop.security.authentication.util.SignerSecretProvider; +import org.apache.hadoop.security.authentication.util.StringSignerSecretProvider; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; @@ -157,9 +160,14 @@ public class TestAuthenticationFilter { Mockito.when(config.getInitParameterNames()).thenReturn( new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.AUTH_TOKEN_VALIDITY)).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); Assert.assertEquals(PseudoAuthenticationHandler.class, filter.getAuthenticationHandler().getClass()); Assert.assertTrue(filter.isRandomSecret()); + Assert.assertFalse(filter.isCustomSignerSecretProvider()); Assert.assertNull(filter.getCookieDomain()); Assert.assertNull(filter.getCookiePath()); Assert.assertEquals(TOKEN_VALIDITY_SEC, filter.getValidity()); @@ -167,6 +175,26 @@ public class TestAuthenticationFilter { filter.destroy(); } + // string secret + filter = new AuthenticationFilter(); + try { + FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn("simple"); + Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret"); + Mockito.when(config.getInitParameterNames()).thenReturn( + new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, + AuthenticationFilter.SIGNATURE_SECRET)).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); + filter.init(config); + Assert.assertFalse(filter.isRandomSecret()); + Assert.assertFalse(filter.isCustomSignerSecretProvider()); + } finally { + filter.destroy(); + } + // custom secret filter = new AuthenticationFilter(); try { @@ -176,8 +204,26 @@ public class TestAuthenticationFilter { Mockito.when(config.getInitParameterNames()).thenReturn( new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.SIGNATURE_SECRET)).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn( + new SignerSecretProvider() { + @Override + public void init(Properties config, long tokenValidity) { + } + @Override + public byte[] getCurrentSecret() { + return null; + } + @Override + public byte[][] getAllSecrets() { + return null; + } + }); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); Assert.assertFalse(filter.isRandomSecret()); + Assert.assertTrue(filter.isCustomSignerSecretProvider()); } finally { filter.destroy(); } @@ -193,6 +239,10 @@ public class TestAuthenticationFilter { new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.COOKIE_DOMAIN, AuthenticationFilter.COOKIE_PATH)).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); Assert.assertEquals(".foo.com", filter.getCookieDomain()); Assert.assertEquals("/bar", filter.getCookiePath()); @@ -213,6 +263,10 @@ public class TestAuthenticationFilter { new Vector( Arrays.asList(AuthenticationFilter.AUTH_TYPE, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); Assert.assertTrue(DummyAuthenticationHandler.init); } finally { @@ -248,6 +302,10 @@ public class TestAuthenticationFilter { Mockito.when(config.getInitParameterNames()).thenReturn( new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.AUTH_TOKEN_VALIDITY)).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); Assert.assertEquals(PseudoAuthenticationHandler.class, @@ -270,6 +328,10 @@ public class TestAuthenticationFilter { new Vector( Arrays.asList(AuthenticationFilter.AUTH_TYPE, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -297,11 +359,15 @@ public class TestAuthenticationFilter { Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.SIGNATURE_SECRET, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); AuthenticationToken token = new AuthenticationToken("u", "p", DummyAuthenticationHandler.TYPE); token.setExpires(System.currentTimeMillis() + TOKEN_VALIDITY_SEC); - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String tokenSigned = signer.sign(token.toString()); Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned); @@ -330,12 +396,16 @@ public class TestAuthenticationFilter { Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.SIGNATURE_SECRET, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); AuthenticationToken token = new AuthenticationToken("u", "p", DummyAuthenticationHandler.TYPE); token.setExpires(System.currentTimeMillis() - TOKEN_VALIDITY_SEC); - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String tokenSigned = signer.sign(token.toString()); Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned); @@ -371,11 +441,15 @@ public class TestAuthenticationFilter { Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.SIGNATURE_SECRET, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); AuthenticationToken token = new AuthenticationToken("u", "p", "invalidtype"); token.setExpires(System.currentTimeMillis() + TOKEN_VALIDITY_SEC); - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String tokenSigned = signer.sign(token.toString()); Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned); @@ -409,6 +483,10 @@ public class TestAuthenticationFilter { new Vector( Arrays.asList(AuthenticationFilter.AUTH_TYPE, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -458,6 +536,10 @@ public class TestAuthenticationFilter { AuthenticationFilter.AUTH_TOKEN_VALIDITY, AuthenticationFilter.SIGNATURE_SECRET, "management.operation" + ".return", "expired.token")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); if (withDomainPath) { Mockito.when(config.getInitParameter(AuthenticationFilter @@ -511,7 +593,7 @@ public class TestAuthenticationFilter { Mockito.verify(chain).doFilter(Mockito.any(ServletRequest.class), Mockito.any(ServletResponse.class)); - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String value = signer.verifyAndExtract(v); AuthenticationToken token = AuthenticationToken.parse(value); assertThat(token.getExpires(), not(0L)); @@ -578,6 +660,10 @@ public class TestAuthenticationFilter { new Vector( Arrays.asList(AuthenticationFilter.AUTH_TYPE, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -585,7 +671,7 @@ public class TestAuthenticationFilter { AuthenticationToken token = new AuthenticationToken("u", "p", "t"); token.setExpires(System.currentTimeMillis() + TOKEN_VALIDITY_SEC); - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String tokenSigned = signer.sign(token.toString()); Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned); @@ -628,6 +714,10 @@ public class TestAuthenticationFilter { new Vector( Arrays.asList(AuthenticationFilter.AUTH_TYPE, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -691,6 +781,10 @@ public class TestAuthenticationFilter { Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.SIGNATURE_SECRET, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -698,7 +792,7 @@ public class TestAuthenticationFilter { AuthenticationToken token = new AuthenticationToken("u", "p", DummyAuthenticationHandler.TYPE); token.setExpires(System.currentTimeMillis() - TOKEN_VALIDITY_SEC); - Signer signer = new Signer(secret.getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider(secret)); String tokenSigned = signer.sign(token.toString()); Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned); @@ -758,6 +852,10 @@ public class TestAuthenticationFilter { Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.SIGNATURE_SECRET, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -765,7 +863,7 @@ public class TestAuthenticationFilter { AuthenticationToken token = new AuthenticationToken("u", "p", "invalidtype"); token.setExpires(System.currentTimeMillis() + TOKEN_VALIDITY_SEC); - Signer signer = new Signer(secret.getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider(secret)); String tokenSigned = signer.sign(token.toString()); Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned); @@ -793,6 +891,10 @@ public class TestAuthenticationFilter { new Vector( Arrays.asList(AuthenticationFilter.AUTH_TYPE, "management.operation.return")).elements()); + ServletContext context = Mockito.mock(ServletContext.class); + Mockito.when(context.getAttribute( + AuthenticationFilter.SIGNATURE_PROVIDER_ATTRIBUTE)).thenReturn(null); + Mockito.when(config.getServletContext()).thenReturn(context); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -812,7 +914,7 @@ public class TestAuthenticationFilter { AuthenticationToken token = new AuthenticationToken("u", "p", "t"); token.setExpires(System.currentTimeMillis() + TOKEN_VALIDITY_SEC); - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String tokenSigned = signer.sign(token.toString()); Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned); Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie}); Modified: hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestSigner.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/util/TestSigner.java?rev=1616006&r1=1616005&r2=1616006&view=diff ============================================================================== --- hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestSigner.java (original) +++ hadoop/common/branches/branch-2/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/util/TestSigner.java Tue Aug 5 21:25:22 2014 @@ -13,24 +13,15 @@ */ package org.apache.hadoop.security.authentication.util; +import java.util.Properties; import org.junit.Assert; import org.junit.Test; public class TestSigner { @Test - public void testNoSecret() throws Exception { - try { - new Signer(null); - Assert.fail(); - } - catch (IllegalArgumentException ex) { - } - } - - @Test public void testNullAndEmptyString() throws Exception { - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); try { signer.sign(null); Assert.fail(); @@ -51,17 +42,17 @@ public class TestSigner { @Test public void testSignature() throws Exception { - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String s1 = signer.sign("ok"); String s2 = signer.sign("ok"); String s3 = signer.sign("wrong"); Assert.assertEquals(s1, s2); - Assert.assertNotSame(s1, s3); + Assert.assertNotEquals(s1, s3); } @Test public void testVerify() throws Exception { - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String t = "test"; String s = signer.sign(t); String e = signer.verifyAndExtract(s); @@ -70,7 +61,7 @@ public class TestSigner { @Test public void testInvalidSignedText() throws Exception { - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); try { signer.verifyAndExtract("test"); Assert.fail(); @@ -83,7 +74,7 @@ public class TestSigner { @Test public void testTampering() throws Exception { - Signer signer = new Signer("secret".getBytes()); + Signer signer = new Signer(new StringSignerSecretProvider("secret")); String t = "test"; String s = signer.sign(t); s += "x"; @@ -96,4 +87,66 @@ public class TestSigner { Assert.fail(); } } + + @Test + public void testMultipleSecrets() throws Exception { + TestSignerSecretProvider secretProvider = new TestSignerSecretProvider(); + Signer signer = new Signer(secretProvider); + secretProvider.setCurrentSecret("secretB"); + String t1 = "test"; + String s1 = signer.sign(t1); + String e1 = signer.verifyAndExtract(s1); + Assert.assertEquals(t1, e1); + secretProvider.setPreviousSecret("secretA"); + String t2 = "test"; + String s2 = signer.sign(t2); + String e2 = signer.verifyAndExtract(s2); + Assert.assertEquals(t2, e2); + Assert.assertEquals(s1, s2); //check is using current secret for signing + secretProvider.setCurrentSecret("secretC"); + secretProvider.setPreviousSecret("secretB"); + String t3 = "test"; + String s3 = signer.sign(t3); + String e3 = signer.verifyAndExtract(s3); + Assert.assertEquals(t3, e3); + Assert.assertNotEquals(s1, s3); //check not using current secret for signing + String e1b = signer.verifyAndExtract(s1); + Assert.assertEquals(t1, e1b); // previous secret still valid + secretProvider.setCurrentSecret("secretD"); + secretProvider.setPreviousSecret("secretC"); + try { + signer.verifyAndExtract(s1); // previous secret no longer valid + Assert.fail(); + } catch (SignerException ex) { + // Expected + } + } + + class TestSignerSecretProvider extends SignerSecretProvider { + + private byte[] currentSecret; + private byte[] previousSecret; + + @Override + public void init(Properties config, long tokenValidity) { + } + + @Override + public byte[] getCurrentSecret() { + return currentSecret; + } + + @Override + public byte[][] getAllSecrets() { + return new byte[][]{currentSecret, previousSecret}; + } + + public void setCurrentSecret(String secretStr) { + currentSecret = secretStr.getBytes(); + } + + public void setPreviousSecret(String previousSecretStr) { + previousSecret = previousSecretStr.getBytes(); + } + } } 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=1616006&r1=1616005&r2=1616006&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 Tue Aug 5 21:25:22 2014 @@ -60,6 +60,9 @@ Release 2.6.0 - UNRELEASED HADOOP-10903. Enhance hadoop classpath command to expand wildcards or write classpath into jar manifest. (cnauroth) + HADOOP-10791. AuthenticationFilter should support externalizing the + secret for signing and provide rotation support. (rkanter via tucu) + OPTIMIZATIONS BUG FIXES