HADOOP-12587. Hadoop AuthToken refuses to work without a maxinactive attribute in issued token. (Benoy Antony)

(cherry picked from commit dec8dfdfa6)
This commit is contained in:
Benoy Antony 2016-01-09 13:39:18 -08:00
parent 80734bc346
commit 4e5f77b7f5
4 changed files with 161 additions and 35 deletions

View File

@ -150,7 +150,7 @@ public class AuthenticationFilter implements Filter {
* that indicates the max inactive interval of the generated token. * that indicates the max inactive interval of the generated token.
*/ */
public static final String public static final String
AUTH_TOKEN_MAX_INACTIVE_INTERVAL = "token.MaxInactiveInterval"; AUTH_TOKEN_MAX_INACTIVE_INTERVAL = "token.max-inactive-interval";
/** /**
* Constant for the configuration property that indicates the validity of the generated token. * Constant for the configuration property that indicates the validity of the generated token.
@ -234,9 +234,11 @@ public class AuthenticationFilter implements Filter {
} else { } else {
authHandlerClassName = authHandlerName; authHandlerClassName = authHandlerName;
} }
maxInactiveInterval = Long.parseLong(config.getProperty( maxInactiveInterval = Long.parseLong(config.getProperty(
AUTH_TOKEN_MAX_INACTIVE_INTERVAL, "1800")) * 1000; // 30 minutes; AUTH_TOKEN_MAX_INACTIVE_INTERVAL, "-1")); // By default, disable.
if (maxInactiveInterval > 0) {
maxInactiveInterval *= 1000;
}
validity = Long.parseLong(config.getProperty(AUTH_TOKEN_VALIDITY, "36000")) validity = Long.parseLong(config.getProperty(AUTH_TOKEN_VALIDITY, "36000"))
* 1000; //10 hours * 1000; //10 hours
initializeSecretProvider(filterConfig); initializeSecretProvider(filterConfig);
@ -559,7 +561,7 @@ public class AuthenticationFilter implements Filter {
} }
token = authHandler.authenticate(httpRequest, httpResponse); token = authHandler.authenticate(httpRequest, httpResponse);
if (token != null && token != AuthenticationToken.ANONYMOUS) { if (token != null && token != AuthenticationToken.ANONYMOUS) {
if (token.getMaxInactives() != 0) { if (token.getMaxInactives() > 0) {
token.setMaxInactives(System.currentTimeMillis() token.setMaxInactives(System.currentTimeMillis()
+ getMaxInactiveInterval() * 1000); + getMaxInactiveInterval() * 1000);
} }
@ -603,6 +605,7 @@ public class AuthenticationFilter implements Filter {
&& getMaxInactiveInterval() > 0) { && getMaxInactiveInterval() > 0) {
token.setMaxInactives(System.currentTimeMillis() token.setMaxInactives(System.currentTimeMillis()
+ getMaxInactiveInterval() * 1000); + getMaxInactiveInterval() * 1000);
token.setExpires(token.getExpires());
newToken = true; newToken = true;
} }
if (newToken && !token.isExpired() if (newToken && !token.isExpired()

View File

@ -39,8 +39,7 @@ public class AuthToken implements Principal {
private static final String TYPE = "t"; private static final String TYPE = "t";
private final static Set<String> ATTRIBUTES = private final static Set<String> ATTRIBUTES =
new HashSet<String>(Arrays.asList(USER_NAME, PRINCIPAL, new HashSet<>(Arrays.asList(USER_NAME, PRINCIPAL, EXPIRES, TYPE));
MAX_INACTIVES, EXPIRES, TYPE));
private String userName; private String userName;
private String principal; private String principal;
@ -133,8 +132,10 @@ public class AuthToken implements Principal {
sb.append(USER_NAME).append("=").append(getUserName()).append(ATTR_SEPARATOR); sb.append(USER_NAME).append("=").append(getUserName()).append(ATTR_SEPARATOR);
sb.append(PRINCIPAL).append("=").append(getName()).append(ATTR_SEPARATOR); sb.append(PRINCIPAL).append("=").append(getName()).append(ATTR_SEPARATOR);
sb.append(TYPE).append("=").append(getType()).append(ATTR_SEPARATOR); sb.append(TYPE).append("=").append(getType()).append(ATTR_SEPARATOR);
if (getMaxInactives() != -1) {
sb.append(MAX_INACTIVES).append("=") sb.append(MAX_INACTIVES).append("=")
.append(getMaxInactives()).append(ATTR_SEPARATOR); .append(getMaxInactives()).append(ATTR_SEPARATOR);
}
sb.append(EXPIRES).append("=").append(getExpires()); sb.append(EXPIRES).append("=").append(getExpires());
tokenStr = sb.toString(); tokenStr = sb.toString();
} }
@ -209,13 +210,16 @@ public class AuthToken implements Principal {
// remove the signature part, since client doesn't care about it // remove the signature part, since client doesn't care about it
map.remove("s"); map.remove("s");
if (!map.keySet().equals(ATTRIBUTES)) { if (!map.keySet().containsAll(ATTRIBUTES)) {
throw new AuthenticationException("Invalid token string, missing attributes"); throw new AuthenticationException("Invalid token string, missing attributes");
} }
long maxInactives = Long.parseLong(map.get(MAX_INACTIVES));
long expires = Long.parseLong(map.get(EXPIRES)); long expires = Long.parseLong(map.get(EXPIRES));
AuthToken token = new AuthToken(map.get(USER_NAME), map.get(PRINCIPAL), map.get(TYPE)); AuthToken token = new AuthToken(map.get(USER_NAME), map.get(PRINCIPAL), map.get(TYPE));
//process optional attributes
if (map.containsKey(MAX_INACTIVES)) {
long maxInactives = Long.parseLong(map.get(MAX_INACTIVES));
token.setMaxInactives(maxInactives); token.setMaxInactives(maxInactives);
}
token.setExpires(expires); token.setExpires(expires);
return token; return token;
} }

View File

@ -34,7 +34,6 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.hadoop.security.authentication.client.AuthenticatedURL; import org.apache.hadoop.security.authentication.client.AuthenticatedURL;
import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.authentication.client.AuthenticationException;
import org.apache.hadoop.security.authentication.util.Signer; import org.apache.hadoop.security.authentication.util.Signer;
@ -897,11 +896,117 @@ public class TestAuthenticationFilter {
authorized); authorized);
} }
@Test
public void testTokenWithValidActivityInterval() throws Exception {
// Provide token containing valid maxInactive value.
// The token is active.
// The server has maxInactiveInterval configured to -1.(disabled)
// The server shall authorize the access, but should not drop a new cookie
long maxInactives = System.currentTimeMillis()
+ TOKEN_MAX_INACTIVE_INTERVAL;
long expires = System.currentTimeMillis() + TOKEN_VALIDITY_SEC;
_testDoFilterAuthenticationMaxInactiveInterval(
maxInactives,
-1,
expires,
true, //authorized
false //newCookie
);
// Provide token containing valid maxInactive value.
// The token is active.
// The server has maxInactiveInterval configured to value
// greater than 0.(enabled)
// The server shall authorize the access and drop a new cookie
// with renewed activity interval
maxInactives = System.currentTimeMillis()
+ TOKEN_MAX_INACTIVE_INTERVAL;
expires = System.currentTimeMillis() + TOKEN_VALIDITY_SEC;
_testDoFilterAuthenticationMaxInactiveInterval(
maxInactives,
TOKEN_MAX_INACTIVE_INTERVAL,
expires,
true, //authorized
true //newCookie
);
}
@Test
public void testTokenWithExpiredActivityIntervaln() throws Exception {
// Provide token containing invalid maxInactive value.
// The token is inactive.
// The server has maxInactiveInterval configured to -1.(disabled)
// The server should deny access and expire the token.
long maxInactives = System.currentTimeMillis()
- TOKEN_MAX_INACTIVE_INTERVAL;
long expires = System.currentTimeMillis() + TOKEN_VALIDITY_SEC;
_testDoFilterAuthenticationMaxInactiveInterval(
maxInactives,
-1,
expires,
false, //authorized
false //newCookie
);
// Provide token containing invalid maxInactive value.
// The token is inactive.
// The server has maxInactiveInterval configured to value
// greater than 0.(enabled)
// The server should deny access and expire the token.
maxInactives = System.currentTimeMillis()
+ TOKEN_MAX_INACTIVE_INTERVAL;
expires = System.currentTimeMillis() + TOKEN_VALIDITY_SEC;
_testDoFilterAuthenticationMaxInactiveInterval(
maxInactives,
-1,
expires,
true, //authorized
false //newCookie
);
}
@Test
public void testTokenWithNoActivityIntervals()
throws Exception {
// Provide token which does not contain maxInactive value.
// The server has maxInactiveInterval configured to -1.
// The server shall authorize the access, but should not drop a new cookie
long expires = System.currentTimeMillis() + TOKEN_VALIDITY_SEC;
_testDoFilterAuthenticationMaxInactiveInterval(
-1,
-1,
expires,
true, //authorized
false //newCookie
);
// Provide token which does not contain maxInactive value.
// The server has maxInactiveInterval to some value
// The server shall authorize the access and drop a new cookie
// with renewed activity interval
expires = System.currentTimeMillis() + TOKEN_VALIDITY_SEC;
_testDoFilterAuthenticationMaxInactiveInterval(
-1,
TOKEN_MAX_INACTIVE_INTERVAL,
expires,
true, //authorized
true //newCookie
);
}
private void private void
_testDoFilterAuthenticationMaxInactiveInterval(long maxInactives, _testDoFilterAuthenticationMaxInactiveInterval(long maxInactivesInToken,
long expires, long expires,
boolean authorized) boolean authorized)
throws Exception { throws Exception {
_testDoFilterAuthenticationMaxInactiveInterval(maxInactivesInToken,
TOKEN_MAX_INACTIVE_INTERVAL, expires, authorized, true);
}
private void
_testDoFilterAuthenticationMaxInactiveInterval(long maxInactivesInToken,
long maxInactivesOnServer,
long expires,
boolean authorized,
boolean newCookie)
throws Exception {
String secret = "secret"; String secret = "secret";
AuthenticationFilter filter = new AuthenticationFilter(); AuthenticationFilter filter = new AuthenticationFilter();
try { try {
@ -913,10 +1018,14 @@ public class TestAuthenticationFilter {
DummyAuthenticationHandler.class.getName()); DummyAuthenticationHandler.class.getName());
Mockito.when(config.getInitParameter( Mockito.when(config.getInitParameter(
AuthenticationFilter.SIGNATURE_SECRET)).thenReturn(secret); AuthenticationFilter.SIGNATURE_SECRET)).thenReturn(secret);
Mockito.when(config.getInitParameter(
AuthenticationFilter.AUTH_TOKEN_MAX_INACTIVE_INTERVAL)).thenReturn(
Long.toString(maxInactivesOnServer));
Mockito.when(config.getInitParameterNames()).thenReturn( Mockito.when(config.getInitParameterNames()).thenReturn(
new Vector<String>( new Vector<String>(
Arrays.asList(AuthenticationFilter.AUTH_TYPE, Arrays.asList(AuthenticationFilter.AUTH_TYPE,
AuthenticationFilter.SIGNATURE_SECRET, AuthenticationFilter.SIGNATURE_SECRET,
AuthenticationFilter.AUTH_TOKEN_MAX_INACTIVE_INTERVAL,
"management.operation.return")).elements()); "management.operation.return")).elements());
getMockedServletContextWithStringSigner(config); getMockedServletContextWithStringSigner(config);
filter.init(config); filter.init(config);
@ -927,7 +1036,7 @@ public class TestAuthenticationFilter {
AuthenticationToken token = new AuthenticationToken("u", "p", AuthenticationToken token = new AuthenticationToken("u", "p",
DummyAuthenticationHandler.TYPE); DummyAuthenticationHandler.TYPE);
token.setMaxInactives(maxInactives); token.setMaxInactives(maxInactivesInToken);
token.setExpires(expires); token.setExpires(expires);
SignerSecretProvider secretProvider = SignerSecretProvider secretProvider =
@ -947,7 +1056,7 @@ public class TestAuthenticationFilter {
FilterChain chain = Mockito.mock(FilterChain.class); FilterChain chain = Mockito.mock(FilterChain.class);
if (authorized) { if (authorized) {
verifyAuthorized(filter, request, response, chain); verifyAuthorized(filter, request, response, chain, newCookie);
} else { } else {
verifyUnauthorized(filter, request, response, chain); verifyUnauthorized(filter, request, response, chain);
} }
@ -959,7 +1068,8 @@ public class TestAuthenticationFilter {
private static void verifyAuthorized(AuthenticationFilter filter, private static void verifyAuthorized(AuthenticationFilter filter,
HttpServletRequest request, HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
FilterChain chain) throws FilterChain chain,
boolean newCookie) throws
Exception { Exception {
final Map<String, String> cookieMap = new HashMap<>(); final Map<String, String> cookieMap = new HashMap<>();
Mockito.doAnswer(new Answer<Object>() { Mockito.doAnswer(new Answer<Object>() {
@ -973,6 +1083,8 @@ public class TestAuthenticationFilter {
filter.doFilter(request, response, chain); filter.doFilter(request, response, chain);
if (newCookie) {
// a new cookie should be dropped when maxInactiveInterval is enabled
String v = cookieMap.get(AuthenticatedURL.AUTH_COOKIE); String v = cookieMap.get(AuthenticatedURL.AUTH_COOKIE);
Assert.assertNotNull("cookie missing", v); Assert.assertNotNull("cookie missing", v);
Assert.assertTrue(v.contains("u=") && v.contains("p=") && v.contains Assert.assertTrue(v.contains("u=") && v.contains("p=") && v.contains
@ -993,6 +1105,12 @@ public class TestAuthenticationFilter {
assertThat(token.getMaxInactives(), not(0L)); assertThat(token.getMaxInactives(), not(0L));
assertThat(token.getExpires(), not(0L)); assertThat(token.getExpires(), not(0L));
Assert.assertFalse("Token is expired.", token.isExpired()); Assert.assertFalse("Token is expired.", token.isExpired());
} else {
//make sure that no auth cookie is dropped.
//For unauthorized response, auth cookie is dropped with empty value
Assert.assertTrue("cookie is present",
!cookieMap.containsKey(AuthenticatedURL.AUTH_COOKIE));
}
} }
private static void verifyUnauthorized(AuthenticationFilter filter, private static void verifyUnauthorized(AuthenticationFilter filter,
@ -1001,6 +1119,7 @@ public class TestAuthenticationFilter {
FilterChain chain) throws FilterChain chain) throws
IOException, IOException,
ServletException { ServletException {
//For unauthorized response, a cookie is dropped with empty string as value
final Map<String, String> cookieMap = new HashMap<String, String>(); final Map<String, String> cookieMap = new HashMap<String, String>();
Mockito.doAnswer(new Answer<Object>() { Mockito.doAnswer(new Answer<Object>() {
@Override @Override

View File

@ -44,7 +44,7 @@ The following properties should be in the `core-site.xml` of all the nodes in th
| `hadoop.http.filter.initializers` | | Add to this property the `org.apache.hadoop.security.AuthenticationFilterInitializer` initializer class. | | `hadoop.http.filter.initializers` | | Add to this property the `org.apache.hadoop.security.AuthenticationFilterInitializer` initializer class. |
| `hadoop.http.authentication.type` | `simple` | Defines authentication used for the HTTP web-consoles. The supported values are: `simple` \| `kerberos` \| `#AUTHENTICATION_HANDLER_CLASSNAME#`. | | `hadoop.http.authentication.type` | `simple` | Defines authentication used for the HTTP web-consoles. The supported values are: `simple` \| `kerberos` \| `#AUTHENTICATION_HANDLER_CLASSNAME#`. |
| `hadoop.http.authentication.token.validity` | `36000` | Indicates how long (in seconds) an authentication token is valid before it has to be renewed. | | `hadoop.http.authentication.token.validity` | `36000` | Indicates how long (in seconds) an authentication token is valid before it has to be renewed. |
| `hadoop.http.authentication.token.MaxInactiveInterval` | `1800` (30 minutes) | Specifies the time, in seconds, between client requests the server will invalidate the token. | | `hadoop.http.authentication.token.max-inactive-interval` | `-1` (disabled) | Specifies the time, in seconds, between client requests the server will invalidate the token. |
| `hadoop.http.authentication.signature.secret.file` | `$user.home/hadoop-http-auth-signature-secret` | The signature secret file for signing the authentication tokens. The same secret should be used for all nodes in the cluster, JobTracker, NameNode, DataNode and TastTracker. This file should be readable only by the Unix user running the daemons. | | `hadoop.http.authentication.signature.secret.file` | `$user.home/hadoop-http-auth-signature-secret` | The signature secret file for signing the authentication tokens. The same secret should be used for all nodes in the cluster, JobTracker, NameNode, DataNode and TastTracker. This file should be readable only by the Unix user running the daemons. |
| `hadoop.http.authentication.cookie.domain` | | The domain to use for the HTTP cookie that stores the authentication token. For authentication to work correctly across all nodes in the cluster the domain must be correctly set. There is no default value, the HTTP cookie will not have a domain working only with the hostname issuing the HTTP cookie. | | `hadoop.http.authentication.cookie.domain` | | The domain to use for the HTTP cookie that stores the authentication token. For authentication to work correctly across all nodes in the cluster the domain must be correctly set. There is no default value, the HTTP cookie will not have a domain working only with the hostname issuing the HTTP cookie. |
| `hadoop.http.authentication.cookie.persistent` | `false` (session cookie) | Specifies the persistence of the HTTP cookie. If the value is true, the cookie is a persistent one. Otherwise, it is a session cookie. *IMPORTANT*: when using IP addresses, browsers ignore cookies with domain settings. For this setting to work properly all nodes in the cluster must be configured to generate URLs with `hostname.domain` names on it. | | `hadoop.http.authentication.cookie.persistent` | `false` (session cookie) | Specifies the persistence of the HTTP cookie. If the value is true, the cookie is a persistent one. Otherwise, it is a session cookie. *IMPORTANT*: when using IP addresses, browsers ignore cookies with domain settings. For this setting to work properly all nodes in the cluster must be configured to generate URLs with `hostname.domain` names on it. |