HADOOP-10880. Move HTTP delegation tokens out of URL querystring to a header. (tucu)
This commit is contained in:
parent
c4c9a78411
commit
d1ae479aa5
|
@ -518,6 +518,9 @@ Release 2.6.0 - UNRELEASED
|
||||||
|
|
||||||
HADOOP-10998. Fix bash tab completion code to work (Jim Hester via aw)
|
HADOOP-10998. Fix bash tab completion code to work (Jim Hester via aw)
|
||||||
|
|
||||||
|
HADOOP-10880. Move HTTP delegation tokens out of URL querystring to
|
||||||
|
a header. (tucu)
|
||||||
|
|
||||||
OPTIMIZATIONS
|
OPTIMIZATIONS
|
||||||
|
|
||||||
HADOOP-10838. Byte array native checksumming. (James Thomas via todd)
|
HADOOP-10838. Byte array native checksumming. (James Thomas via todd)
|
||||||
|
|
|
@ -125,6 +125,8 @@ public class DelegationTokenAuthenticatedURL extends AuthenticatedURL {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean useQueryStringforDelegationToken = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an <code>DelegationTokenAuthenticatedURL</code>.
|
* Creates an <code>DelegationTokenAuthenticatedURL</code>.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
@ -170,6 +172,34 @@ public class DelegationTokenAuthenticatedURL extends AuthenticatedURL {
|
||||||
super(obtainDelegationTokenAuthenticator(authenticator), connConfigurator);
|
super(obtainDelegationTokenAuthenticator(authenticator), connConfigurator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets if delegation token should be transmitted in the URL query string.
|
||||||
|
* By default it is transmitted using the
|
||||||
|
* {@link DelegationTokenAuthenticator#DELEGATION_TOKEN_HEADER} HTTP header.
|
||||||
|
* <p/>
|
||||||
|
* This method is provided to enable WebHDFS backwards compatibility.
|
||||||
|
*
|
||||||
|
* @param useQueryString <code>TRUE</code> if the token is transmitted in the
|
||||||
|
* URL query string, <code>FALSE</code> if the delegation token is transmitted
|
||||||
|
* using the {@link DelegationTokenAuthenticator#DELEGATION_TOKEN_HEADER} HTTP
|
||||||
|
* header.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
protected void setUseQueryStringForDelegationToken(boolean useQueryString) {
|
||||||
|
useQueryStringforDelegationToken = useQueryString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if delegation token is transmitted as a HTTP header.
|
||||||
|
*
|
||||||
|
* @return <code>TRUE</code> if the token is transmitted in the URL query
|
||||||
|
* string, <code>FALSE</code> if the delegation token is transmitted using the
|
||||||
|
* {@link DelegationTokenAuthenticator#DELEGATION_TOKEN_HEADER} HTTP header.
|
||||||
|
*/
|
||||||
|
public boolean useQueryStringForDelegationToken() {
|
||||||
|
return useQueryStringforDelegationToken;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an authenticated {@link HttpURLConnection}, it uses a Delegation
|
* Returns an authenticated {@link HttpURLConnection}, it uses a Delegation
|
||||||
* Token only if the given auth token is an instance of {@link Token} and
|
* Token only if the given auth token is an instance of {@link Token} and
|
||||||
|
@ -235,23 +265,41 @@ public class DelegationTokenAuthenticatedURL extends AuthenticatedURL {
|
||||||
* @throws IOException if an IO error occurred.
|
* @throws IOException if an IO error occurred.
|
||||||
* @throws AuthenticationException if an authentication exception occurred.
|
* @throws AuthenticationException if an authentication exception occurred.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public HttpURLConnection openConnection(URL url, Token token, String doAs)
|
public HttpURLConnection openConnection(URL url, Token token, String doAs)
|
||||||
throws IOException, AuthenticationException {
|
throws IOException, AuthenticationException {
|
||||||
Preconditions.checkNotNull(url, "url");
|
Preconditions.checkNotNull(url, "url");
|
||||||
Preconditions.checkNotNull(token, "token");
|
Preconditions.checkNotNull(token, "token");
|
||||||
Map<String, String> extraParams = new HashMap<String, String>();
|
Map<String, String> extraParams = new HashMap<String, String>();
|
||||||
|
org.apache.hadoop.security.token.Token<? extends TokenIdentifier> dToken
|
||||||
|
= null;
|
||||||
|
// if we have valid auth token, it takes precedence over a delegation token
|
||||||
|
// and we don't even look for one.
|
||||||
|
if (!token.isSet()) {
|
||||||
// delegation token
|
// delegation token
|
||||||
Credentials creds = UserGroupInformation.getCurrentUser().getCredentials();
|
Credentials creds = UserGroupInformation.getCurrentUser().
|
||||||
|
getCredentials();
|
||||||
if (!creds.getAllTokens().isEmpty()) {
|
if (!creds.getAllTokens().isEmpty()) {
|
||||||
InetSocketAddress serviceAddr = new InetSocketAddress(url.getHost(),
|
InetSocketAddress serviceAddr = new InetSocketAddress(url.getHost(),
|
||||||
url.getPort());
|
url.getPort());
|
||||||
Text service = SecurityUtil.buildTokenService(serviceAddr);
|
Text service = SecurityUtil.buildTokenService(serviceAddr);
|
||||||
org.apache.hadoop.security.token.Token<? extends TokenIdentifier> dt =
|
dToken = creds.getToken(service);
|
||||||
creds.getToken(service);
|
if (dToken != null) {
|
||||||
if (dt != null) {
|
if (useQueryStringForDelegationToken()) {
|
||||||
extraParams.put(KerberosDelegationTokenAuthenticator.DELEGATION_PARAM,
|
// delegation token will go in the query string, injecting it
|
||||||
dt.encodeToUrlString());
|
extraParams.put(
|
||||||
|
KerberosDelegationTokenAuthenticator.DELEGATION_PARAM,
|
||||||
|
dToken.encodeToUrlString());
|
||||||
|
} else {
|
||||||
|
// delegation token will go as request header, setting it in the
|
||||||
|
// auth-token to ensure no authentication handshake is triggered
|
||||||
|
// (if we have a delegation token, we are authenticated)
|
||||||
|
// the delegation token header is injected in the connection request
|
||||||
|
// at the end of this method.
|
||||||
|
token.delegationToken = (org.apache.hadoop.security.token.Token
|
||||||
|
<AbstractDelegationTokenIdentifier>) dToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,7 +309,14 @@ public class DelegationTokenAuthenticatedURL extends AuthenticatedURL {
|
||||||
}
|
}
|
||||||
|
|
||||||
url = augmentURL(url, extraParams);
|
url = augmentURL(url, extraParams);
|
||||||
return super.openConnection(url, token);
|
HttpURLConnection conn = super.openConnection(url, token);
|
||||||
|
if (!token.isSet() && !useQueryStringForDelegationToken() && dToken != null) {
|
||||||
|
// injecting the delegation token header in the connection request
|
||||||
|
conn.setRequestProperty(
|
||||||
|
DelegationTokenAuthenticator.DELEGATION_TOKEN_HEADER,
|
||||||
|
dToken.encodeToUrlString());
|
||||||
|
}
|
||||||
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -331,8 +331,7 @@ public abstract class DelegationTokenAuthenticationHandler
|
||||||
HttpServletResponse response)
|
HttpServletResponse response)
|
||||||
throws IOException, AuthenticationException {
|
throws IOException, AuthenticationException {
|
||||||
AuthenticationToken token;
|
AuthenticationToken token;
|
||||||
String delegationParam = ServletUtils.getParameter(request,
|
String delegationParam = getDelegationToken(request);
|
||||||
KerberosDelegationTokenAuthenticator.DELEGATION_PARAM);
|
|
||||||
if (delegationParam != null) {
|
if (delegationParam != null) {
|
||||||
try {
|
try {
|
||||||
Token<DelegationTokenIdentifier> dt =
|
Token<DelegationTokenIdentifier> dt =
|
||||||
|
@ -356,4 +355,15 @@ public abstract class DelegationTokenAuthenticationHandler
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getDelegationToken(HttpServletRequest request)
|
||||||
|
throws IOException {
|
||||||
|
String dToken = request.getHeader(
|
||||||
|
DelegationTokenAuthenticator.DELEGATION_TOKEN_HEADER);
|
||||||
|
if (dToken == null) {
|
||||||
|
dToken = ServletUtils.getParameter(request,
|
||||||
|
KerberosDelegationTokenAuthenticator.DELEGATION_PARAM);
|
||||||
|
}
|
||||||
|
return dToken;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,9 @@ public abstract class DelegationTokenAuthenticator implements Authenticator {
|
||||||
|
|
||||||
public static final String OP_PARAM = "op";
|
public static final String OP_PARAM = "op";
|
||||||
|
|
||||||
|
public static final String DELEGATION_TOKEN_HEADER =
|
||||||
|
"X-Hadoop-Delegation-Token";
|
||||||
|
|
||||||
public static final String DELEGATION_PARAM = "delegation";
|
public static final String DELEGATION_PARAM = "delegation";
|
||||||
public static final String TOKEN_PARAM = "token";
|
public static final String TOKEN_PARAM = "token";
|
||||||
public static final String RENEWER_PARAM = "renewer";
|
public static final String RENEWER_PARAM = "renewer";
|
||||||
|
@ -101,15 +104,23 @@ public abstract class DelegationTokenAuthenticator implements Authenticator {
|
||||||
authenticator.setConnectionConfigurator(configurator);
|
authenticator.setConnectionConfigurator(configurator);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasDelegationToken(URL url) {
|
private boolean hasDelegationToken(URL url, AuthenticatedURL.Token token) {
|
||||||
|
boolean hasDt = false;
|
||||||
|
if (token instanceof DelegationTokenAuthenticatedURL.Token) {
|
||||||
|
hasDt = ((DelegationTokenAuthenticatedURL.Token) token).
|
||||||
|
getDelegationToken() != null;
|
||||||
|
}
|
||||||
|
if (!hasDt) {
|
||||||
String queryStr = url.getQuery();
|
String queryStr = url.getQuery();
|
||||||
return (queryStr != null) && queryStr.contains(DELEGATION_PARAM + "=");
|
hasDt = (queryStr != null) && queryStr.contains(DELEGATION_PARAM + "=");
|
||||||
|
}
|
||||||
|
return hasDt;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void authenticate(URL url, AuthenticatedURL.Token token)
|
public void authenticate(URL url, AuthenticatedURL.Token token)
|
||||||
throws IOException, AuthenticationException {
|
throws IOException, AuthenticationException {
|
||||||
if (!hasDelegationToken(url)) {
|
if (!hasDelegationToken(url, token)) {
|
||||||
authenticator.authenticate(url, token);
|
authenticator.authenticate(url, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,11 +284,13 @@ public class TestDelegationTokenAuthenticationHandlerWithMocks {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAuthenticate() throws Exception {
|
public void testAuthenticate() throws Exception {
|
||||||
testValidDelegationToken();
|
testValidDelegationTokenQueryString();
|
||||||
testInvalidDelegationToken();
|
testValidDelegationTokenHeader();
|
||||||
|
testInvalidDelegationTokenQueryString();
|
||||||
|
testInvalidDelegationTokenHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testValidDelegationToken() throws Exception {
|
private void testValidDelegationTokenQueryString() throws Exception {
|
||||||
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
||||||
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
Token<DelegationTokenIdentifier> dToken =
|
Token<DelegationTokenIdentifier> dToken =
|
||||||
|
@ -307,7 +309,26 @@ public class TestDelegationTokenAuthenticationHandlerWithMocks {
|
||||||
Assert.assertTrue(token.isExpired());
|
Assert.assertTrue(token.isExpired());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testInvalidDelegationToken() throws Exception {
|
private void testValidDelegationTokenHeader() throws Exception {
|
||||||
|
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Token<DelegationTokenIdentifier> dToken =
|
||||||
|
handler.getTokenManager().createToken(
|
||||||
|
UserGroupInformation.getCurrentUser(), "user");
|
||||||
|
Mockito.when(request.getHeader(Mockito.eq(
|
||||||
|
DelegationTokenAuthenticator.DELEGATION_TOKEN_HEADER))).thenReturn(
|
||||||
|
dToken.encodeToUrlString());
|
||||||
|
|
||||||
|
AuthenticationToken token = handler.authenticate(request, response);
|
||||||
|
Assert.assertEquals(UserGroupInformation.getCurrentUser().
|
||||||
|
getShortUserName(), token.getUserName());
|
||||||
|
Assert.assertEquals(0, token.getExpires());
|
||||||
|
Assert.assertEquals(handler.getType(),
|
||||||
|
token.getType());
|
||||||
|
Assert.assertTrue(token.isExpired());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testInvalidDelegationTokenQueryString() throws Exception {
|
||||||
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
||||||
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
Mockito.when(request.getQueryString()).thenReturn(
|
Mockito.when(request.getQueryString()).thenReturn(
|
||||||
|
@ -323,4 +344,21 @@ public class TestDelegationTokenAuthenticationHandlerWithMocks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void testInvalidDelegationTokenHeader() throws Exception {
|
||||||
|
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
|
||||||
|
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
|
||||||
|
Mockito.when(request.getHeader(Mockito.eq(
|
||||||
|
DelegationTokenAuthenticator.DELEGATION_TOKEN_HEADER))).thenReturn(
|
||||||
|
"invalid");
|
||||||
|
|
||||||
|
try {
|
||||||
|
handler.authenticate(request, response);
|
||||||
|
Assert.fail();
|
||||||
|
} catch (AuthenticationException ex) {
|
||||||
|
//NOP
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,6 +149,15 @@ public class TestWebDelegationToken {
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
resp.setStatus(HttpServletResponse.SC_OK);
|
resp.setStatus(HttpServletResponse.SC_OK);
|
||||||
resp.getWriter().write("ping");
|
resp.getWriter().write("ping");
|
||||||
|
if (req.getHeader(DelegationTokenAuthenticator.DELEGATION_TOKEN_HEADER)
|
||||||
|
!= null) {
|
||||||
|
resp.setHeader("UsingHeader", "true");
|
||||||
|
}
|
||||||
|
if (req.getQueryString() != null &&
|
||||||
|
req.getQueryString().contains(
|
||||||
|
DelegationTokenAuthenticator.DELEGATION_PARAM + "=")) {
|
||||||
|
resp.setHeader("UsingQueryString", "true");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -314,7 +323,20 @@ public class TestWebDelegationToken {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDelegationTokenAuthenticatorCalls() throws Exception {
|
public void testDelegationTokenAuthenticatorCallsWithHeader()
|
||||||
|
throws Exception {
|
||||||
|
testDelegationTokenAuthenticatorCalls(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDelegationTokenAuthenticatorCallsWithQueryString()
|
||||||
|
throws Exception {
|
||||||
|
testDelegationTokenAuthenticatorCalls(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void testDelegationTokenAuthenticatorCalls(final boolean useQS)
|
||||||
|
throws Exception {
|
||||||
final Server jetty = createJettyServer();
|
final Server jetty = createJettyServer();
|
||||||
Context context = new Context();
|
Context context = new Context();
|
||||||
context.setContextPath("/foo");
|
context.setContextPath("/foo");
|
||||||
|
@ -324,14 +346,15 @@ public class TestWebDelegationToken {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
jetty.start();
|
jetty.start();
|
||||||
URL nonAuthURL = new URL(getJettyURL() + "/foo/bar");
|
final URL nonAuthURL = new URL(getJettyURL() + "/foo/bar");
|
||||||
URL authURL = new URL(getJettyURL() + "/foo/bar?authenticated=foo");
|
URL authURL = new URL(getJettyURL() + "/foo/bar?authenticated=foo");
|
||||||
URL authURL2 = new URL(getJettyURL() + "/foo/bar?authenticated=bar");
|
URL authURL2 = new URL(getJettyURL() + "/foo/bar?authenticated=bar");
|
||||||
|
|
||||||
DelegationTokenAuthenticatedURL.Token token =
|
DelegationTokenAuthenticatedURL.Token token =
|
||||||
new DelegationTokenAuthenticatedURL.Token();
|
new DelegationTokenAuthenticatedURL.Token();
|
||||||
DelegationTokenAuthenticatedURL aUrl =
|
final DelegationTokenAuthenticatedURL aUrl =
|
||||||
new DelegationTokenAuthenticatedURL();
|
new DelegationTokenAuthenticatedURL();
|
||||||
|
aUrl.setUseQueryStringForDelegationToken(useQS);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
aUrl.getDelegationToken(nonAuthURL, token, FOO_USER);
|
aUrl.getDelegationToken(nonAuthURL, token, FOO_USER);
|
||||||
|
@ -379,6 +402,27 @@ public class TestWebDelegationToken {
|
||||||
Assert.assertTrue(ex.getMessage().contains("401"));
|
Assert.assertTrue(ex.getMessage().contains("401"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
aUrl.getDelegationToken(authURL, token, "foo");
|
||||||
|
|
||||||
|
UserGroupInformation ugi = UserGroupInformation.getCurrentUser();
|
||||||
|
ugi.addToken(token.getDelegationToken());
|
||||||
|
ugi.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
HttpURLConnection conn = aUrl.openConnection(nonAuthURL, new DelegationTokenAuthenticatedURL.Token());
|
||||||
|
Assert.assertEquals(HttpServletResponse.SC_OK, conn.getResponseCode());
|
||||||
|
if (useQS) {
|
||||||
|
Assert.assertNull(conn.getHeaderField("UsingHeader"));
|
||||||
|
Assert.assertNotNull(conn.getHeaderField("UsingQueryString"));
|
||||||
|
} else {
|
||||||
|
Assert.assertNotNull(conn.getHeaderField("UsingHeader"));
|
||||||
|
Assert.assertNull(conn.getHeaderField("UsingQueryString"));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
jetty.stop();
|
jetty.stop();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue