| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.hadoop.security.token.delegation.web; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.hadoop.io.Text; |
| import org.apache.hadoop.minikdc.MiniKdc; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.security.authentication.client.AuthenticationException; |
| import org.apache.hadoop.security.authentication.client.KerberosAuthenticator; |
| import org.apache.hadoop.security.authentication.server.AuthenticationFilter; |
| import org.apache.hadoop.security.authentication.server.AuthenticationHandler; |
| import org.apache.hadoop.security.authentication.server.AuthenticationToken; |
| import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler; |
| import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler; |
| import org.apache.hadoop.security.authentication.util.KerberosUtil; |
| import org.apache.hadoop.security.token.delegation.AbstractDelegationTokenSecretManager; |
| import org.codehaus.jackson.map.ObjectMapper; |
| import org.apache.hadoop.test.GenericTestUtils; |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.mortbay.jetty.AbstractConnector; |
| import org.mortbay.jetty.Connector; |
| import org.mortbay.jetty.Server; |
| import org.mortbay.jetty.servlet.Context; |
| import org.mortbay.jetty.servlet.FilterHolder; |
| import org.mortbay.jetty.servlet.ServletHolder; |
| import org.slf4j.event.Level; |
| |
| import javax.security.auth.Subject; |
| import javax.security.auth.kerberos.KerberosPrincipal; |
| import javax.security.auth.login.AppConfigurationEntry; |
| import javax.security.auth.login.Configuration; |
| import javax.security.auth.login.LoginContext; |
| import javax.servlet.Filter; |
| import javax.servlet.FilterConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.DataInputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.Writer; |
| import java.net.HttpURLConnection; |
| import java.net.InetAddress; |
| import java.net.ServerSocket; |
| import java.net.URL; |
| import java.security.Principal; |
| import java.security.PrivilegedActionException; |
| import java.security.PrivilegedExceptionAction; |
| 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.UUID; |
| import java.util.concurrent.Callable; |
| |
| public class TestWebDelegationToken { |
| private static final String OK_USER = "ok-user"; |
| private static final String FAIL_USER = "fail-user"; |
| private static final String FOO_USER = "foo"; |
| |
| private Server jetty; |
| |
| public static class DummyAuthenticationHandler |
| implements AuthenticationHandler { |
| @Override |
| public String getType() { |
| return "dummy"; |
| } |
| |
| @Override |
| public void init(Properties config) throws ServletException { |
| } |
| |
| @Override |
| public void destroy() { |
| } |
| |
| @Override |
| public boolean managementOperation(AuthenticationToken token, |
| HttpServletRequest request, HttpServletResponse response) |
| throws IOException, AuthenticationException { |
| return false; |
| } |
| |
| @Override |
| public AuthenticationToken authenticate(HttpServletRequest request, |
| HttpServletResponse response) |
| throws IOException, AuthenticationException { |
| AuthenticationToken token = null; |
| if (request.getParameter("authenticated") != null) { |
| token = new AuthenticationToken(request.getParameter("authenticated"), |
| "U", "test"); |
| } else { |
| response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); |
| response.setHeader(KerberosAuthenticator.WWW_AUTHENTICATE, "dummy"); |
| } |
| return token; |
| } |
| } |
| |
| public static class DummyDelegationTokenAuthenticationHandler extends |
| DelegationTokenAuthenticationHandler { |
| public DummyDelegationTokenAuthenticationHandler() { |
| super(new DummyAuthenticationHandler()); |
| } |
| |
| @Override |
| public void init(Properties config) throws ServletException { |
| Properties conf = new Properties(config); |
| conf.setProperty(TOKEN_KIND, "token-kind"); |
| initTokenManager(conf); |
| } |
| } |
| |
| public static class AFilter extends DelegationTokenAuthenticationFilter { |
| |
| @Override |
| protected Properties getConfiguration(String configPrefix, |
| FilterConfig filterConfig) { |
| Properties conf = new Properties(); |
| conf.setProperty(AUTH_TYPE, |
| DummyDelegationTokenAuthenticationHandler.class.getName()); |
| return conf; |
| } |
| } |
| |
| public static class PingServlet extends HttpServlet { |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) |
| throws ServletException, IOException { |
| resp.setStatus(HttpServletResponse.SC_OK); |
| 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 |
| protected void doPost(HttpServletRequest req, HttpServletResponse resp) |
| throws ServletException, IOException { |
| Writer writer = resp.getWriter(); |
| writer.write("ping: "); |
| IOUtils.copy(req.getReader(), writer); |
| resp.setStatus(HttpServletResponse.SC_OK); |
| } |
| } |
| |
| protected Server createJettyServer() { |
| try { |
| jetty = new Server(0); |
| jetty.getConnectors()[0].setHost("localhost"); |
| return jetty; |
| } catch (Exception ex) { |
| throw new RuntimeException("Could not setup Jetty: " + ex.getMessage(), |
| ex); |
| } |
| } |
| |
| protected String getJettyURL() { |
| Connector c = jetty.getConnectors()[0]; |
| return "http://" + c.getHost() + ":" + c.getLocalPort(); |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| // resetting hadoop security to simple |
| org.apache.hadoop.conf.Configuration conf = |
| new org.apache.hadoop.conf.Configuration(); |
| UserGroupInformation.setConfiguration(conf); |
| |
| jetty = createJettyServer(); |
| GenericTestUtils.setLogLevel(KerberosAuthenticationHandler.LOG, |
| Level.TRACE); |
| } |
| |
| @After |
| public void cleanUp() throws Exception { |
| jetty.stop(); |
| |
| // resetting hadoop security to simple |
| org.apache.hadoop.conf.Configuration conf = |
| new org.apache.hadoop.conf.Configuration(); |
| UserGroupInformation.setConfiguration(conf); |
| } |
| |
| protected Server getJetty() { |
| return jetty; |
| } |
| |
| @Test |
| public void testRawHttpCalls() throws Exception { |
| final Server jetty = createJettyServer(); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| context.addFilter(new FilterHolder(AFilter.class), "/*", 0); |
| context.addServlet(new ServletHolder(PingServlet.class), "/bar"); |
| try { |
| jetty.start(); |
| URL nonAuthURL = new URL(getJettyURL() + "/foo/bar"); |
| URL authURL = new URL(getJettyURL() + "/foo/bar?authenticated=foo"); |
| |
| // unauthenticated access to URL |
| HttpURLConnection conn = (HttpURLConnection) nonAuthURL.openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, |
| conn.getResponseCode()); |
| |
| // authenticated access to URL |
| conn = (HttpURLConnection) authURL.openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| |
| // unauthenticated access to get delegation token |
| URL url = new URL(nonAuthURL.toExternalForm() + "?op=GETDELEGATIONTOKEN"); |
| conn = (HttpURLConnection) url.openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, |
| conn.getResponseCode()); |
| |
| // authenticated access to get delegation token |
| url = new URL(authURL.toExternalForm() + |
| "&op=GETDELEGATIONTOKEN&renewer=foo"); |
| conn = (HttpURLConnection) url.openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| ObjectMapper mapper = new ObjectMapper(); |
| Map map = mapper.readValue(conn.getInputStream(), Map.class); |
| String dt = (String) ((Map) map.get("Token")).get("urlString"); |
| Assert.assertNotNull(dt); |
| |
| // delegation token access to URL |
| url = new URL(nonAuthURL.toExternalForm() + "?delegation=" + dt); |
| conn = (HttpURLConnection) url.openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| |
| // delegation token and authenticated access to URL |
| url = new URL(authURL.toExternalForm() + "&delegation=" + dt); |
| conn = (HttpURLConnection) url.openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| |
| // renewew delegation token, unauthenticated access to URL |
| url = new URL(nonAuthURL.toExternalForm() + |
| "?op=RENEWDELEGATIONTOKEN&token=" + dt); |
| conn = (HttpURLConnection) url.openConnection(); |
| conn.setRequestMethod("PUT"); |
| Assert.assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, |
| conn.getResponseCode()); |
| |
| // renewew delegation token, authenticated access to URL |
| url = new URL(authURL.toExternalForm() + |
| "&op=RENEWDELEGATIONTOKEN&token=" + dt); |
| conn = (HttpURLConnection) url.openConnection(); |
| conn.setRequestMethod("PUT"); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| |
| // renewew delegation token, authenticated access to URL, not renewer |
| url = new URL(getJettyURL() + |
| "/foo/bar?authenticated=bar&op=RENEWDELEGATIONTOKEN&token=" + dt); |
| conn = (HttpURLConnection) url.openConnection(); |
| conn.setRequestMethod("PUT"); |
| Assert.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, |
| conn.getResponseCode()); |
| |
| // cancel delegation token, nonauthenticated access to URL |
| url = new URL(nonAuthURL.toExternalForm() + |
| "?op=CANCELDELEGATIONTOKEN&token=" + dt); |
| conn = (HttpURLConnection) url.openConnection(); |
| conn.setRequestMethod("PUT"); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| |
| // cancel canceled delegation token, nonauthenticated access to URL |
| url = new URL(nonAuthURL.toExternalForm() + |
| "?op=CANCELDELEGATIONTOKEN&token=" + dt); |
| conn = (HttpURLConnection) url.openConnection(); |
| conn.setRequestMethod("PUT"); |
| Assert.assertEquals(HttpURLConnection.HTTP_NOT_FOUND, |
| conn.getResponseCode()); |
| |
| // get new delegation token |
| url = new URL(authURL.toExternalForm() + |
| "&op=GETDELEGATIONTOKEN&renewer=foo"); |
| conn = (HttpURLConnection) url.openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| mapper = new ObjectMapper(); |
| map = mapper.readValue(conn.getInputStream(), Map.class); |
| dt = (String) ((Map) map.get("Token")).get("urlString"); |
| Assert.assertNotNull(dt); |
| |
| // cancel delegation token, authenticated access to URL |
| url = new URL(authURL.toExternalForm() + |
| "&op=CANCELDELEGATIONTOKEN&token=" + dt); |
| conn = (HttpURLConnection) url.openConnection(); |
| conn.setRequestMethod("PUT"); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| } finally { |
| jetty.stop(); |
| } |
| } |
| |
| @Test |
| 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(); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| context.addFilter(new FilterHolder(AFilter.class), "/*", 0); |
| context.addServlet(new ServletHolder(PingServlet.class), "/bar"); |
| |
| try { |
| jetty.start(); |
| final URL nonAuthURL = new URL(getJettyURL() + "/foo/bar"); |
| URL authURL = new URL(getJettyURL() + "/foo/bar?authenticated=foo"); |
| URL authURL2 = new URL(getJettyURL() + "/foo/bar?authenticated=bar"); |
| |
| DelegationTokenAuthenticatedURL.Token token = |
| new DelegationTokenAuthenticatedURL.Token(); |
| final DelegationTokenAuthenticatedURL aUrl = |
| new DelegationTokenAuthenticatedURL(); |
| aUrl.setUseQueryStringForDelegationToken(useQS); |
| |
| try { |
| aUrl.getDelegationToken(nonAuthURL, token, FOO_USER); |
| Assert.fail(); |
| } catch (Exception ex) { |
| Assert.assertTrue(ex.getMessage().contains("401")); |
| } |
| |
| aUrl.getDelegationToken(authURL, token, FOO_USER); |
| Assert.assertNotNull(token.getDelegationToken()); |
| Assert.assertEquals(new Text("token-kind"), |
| token.getDelegationToken().getKind()); |
| |
| aUrl.renewDelegationToken(authURL, token); |
| |
| try { |
| aUrl.renewDelegationToken(nonAuthURL, token); |
| Assert.fail(); |
| } catch (Exception ex) { |
| Assert.assertTrue(ex.getMessage().contains("401")); |
| } |
| |
| aUrl.getDelegationToken(authURL, token, FOO_USER); |
| |
| try { |
| aUrl.renewDelegationToken(authURL2, token); |
| Assert.fail(); |
| } catch (Exception ex) { |
| Assert.assertTrue(ex.getMessage().contains("403")); |
| } |
| |
| aUrl.getDelegationToken(authURL, token, FOO_USER); |
| |
| aUrl.cancelDelegationToken(authURL, token); |
| |
| aUrl.getDelegationToken(authURL, token, FOO_USER); |
| |
| aUrl.cancelDelegationToken(nonAuthURL, token); |
| |
| aUrl.getDelegationToken(authURL, token, FOO_USER); |
| |
| try { |
| aUrl.renewDelegationToken(nonAuthURL, token); |
| } catch (Exception ex) { |
| 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 { |
| jetty.stop(); |
| } |
| } |
| |
| private static class DummyDelegationTokenSecretManager |
| extends AbstractDelegationTokenSecretManager<DelegationTokenIdentifier> { |
| |
| public DummyDelegationTokenSecretManager() { |
| super(10000, 10000, 10000, 10000); |
| } |
| |
| @Override |
| public DelegationTokenIdentifier createIdentifier() { |
| return new DelegationTokenIdentifier(new Text("fooKind")); |
| } |
| |
| } |
| |
| @Test |
| public void testExternalDelegationTokenSecretManager() throws Exception { |
| DummyDelegationTokenSecretManager secretMgr |
| = new DummyDelegationTokenSecretManager(); |
| final Server jetty = createJettyServer(); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| context.addFilter(new FilterHolder(AFilter.class), "/*", 0); |
| context.addServlet(new ServletHolder(PingServlet.class), "/bar"); |
| try { |
| secretMgr.startThreads(); |
| context.setAttribute(DelegationTokenAuthenticationFilter. |
| DELEGATION_TOKEN_SECRET_MANAGER_ATTR, secretMgr); |
| jetty.start(); |
| URL authURL = new URL(getJettyURL() + "/foo/bar?authenticated=foo"); |
| |
| DelegationTokenAuthenticatedURL.Token token = |
| new DelegationTokenAuthenticatedURL.Token(); |
| DelegationTokenAuthenticatedURL aUrl = |
| new DelegationTokenAuthenticatedURL(); |
| |
| aUrl.getDelegationToken(authURL, token, FOO_USER); |
| Assert.assertNotNull(token.getDelegationToken()); |
| Assert.assertEquals(new Text("fooKind"), |
| token.getDelegationToken().getKind()); |
| |
| } finally { |
| jetty.stop(); |
| secretMgr.stopThreads(); |
| } |
| } |
| |
| public static class NoDTFilter extends AuthenticationFilter { |
| |
| @Override |
| protected Properties getConfiguration(String configPrefix, |
| FilterConfig filterConfig) { |
| Properties conf = new Properties(); |
| conf.setProperty(AUTH_TYPE, PseudoAuthenticationHandler.TYPE); |
| return conf; |
| } |
| } |
| |
| |
| public static class NoDTHandlerDTAFilter |
| extends DelegationTokenAuthenticationFilter { |
| |
| @Override |
| protected Properties getConfiguration(String configPrefix, |
| FilterConfig filterConfig) { |
| Properties conf = new Properties(); |
| conf.setProperty(AUTH_TYPE, PseudoAuthenticationHandler.TYPE); |
| return conf; |
| } |
| } |
| |
| public static class UserServlet extends HttpServlet { |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) |
| throws ServletException, IOException { |
| resp.setStatus(HttpServletResponse.SC_OK); |
| resp.getWriter().write(req.getUserPrincipal().getName()); |
| } |
| } |
| |
| @Test |
| public void testDelegationTokenAuthenticationURLWithNoDTFilter() |
| throws Exception { |
| testDelegationTokenAuthenticatedURLWithNoDT(NoDTFilter.class); |
| } |
| |
| @Test |
| public void testDelegationTokenAuthenticationURLWithNoDTHandler() |
| throws Exception { |
| testDelegationTokenAuthenticatedURLWithNoDT(NoDTHandlerDTAFilter.class); |
| } |
| |
| // we are, also, implicitly testing KerberosDelegationTokenAuthenticator |
| // fallback here |
| private void testDelegationTokenAuthenticatedURLWithNoDT( |
| Class<? extends Filter> filterClass) throws Exception { |
| final Server jetty = createJettyServer(); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| context.addFilter(new FilterHolder(filterClass), "/*", 0); |
| context.addServlet(new ServletHolder(UserServlet.class), "/bar"); |
| |
| try { |
| jetty.start(); |
| final URL url = new URL(getJettyURL() + "/foo/bar"); |
| |
| UserGroupInformation ugi = UserGroupInformation.createRemoteUser(FOO_USER); |
| ugi.doAs(new PrivilegedExceptionAction<Void>() { |
| @Override |
| public Void run() throws Exception { |
| DelegationTokenAuthenticatedURL.Token token = |
| new DelegationTokenAuthenticatedURL.Token(); |
| DelegationTokenAuthenticatedURL aUrl = |
| new DelegationTokenAuthenticatedURL(); |
| HttpURLConnection conn = aUrl.openConnection(url, token); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, |
| conn.getResponseCode()); |
| List<String> ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals(FOO_USER, ret.get(0)); |
| |
| try { |
| aUrl.getDelegationToken(url, token, FOO_USER); |
| Assert.fail(); |
| } catch (AuthenticationException ex) { |
| Assert.assertTrue(ex.getMessage().contains( |
| "delegation token operation")); |
| } |
| return null; |
| } |
| }); |
| } finally { |
| jetty.stop(); |
| } |
| } |
| |
| public static class PseudoDTAFilter |
| extends DelegationTokenAuthenticationFilter { |
| |
| @Override |
| protected Properties getConfiguration(String configPrefix, |
| FilterConfig filterConfig) { |
| Properties conf = new Properties(); |
| conf.setProperty(AUTH_TYPE, |
| PseudoDelegationTokenAuthenticationHandler.class.getName()); |
| conf.setProperty(DelegationTokenAuthenticationHandler.TOKEN_KIND, |
| "token-kind"); |
| return conf; |
| } |
| |
| @Override |
| protected org.apache.hadoop.conf.Configuration getProxyuserConfiguration( |
| FilterConfig filterConfig) throws ServletException { |
| org.apache.hadoop.conf.Configuration conf = |
| new org.apache.hadoop.conf.Configuration(false); |
| conf.set("proxyuser.foo.users", OK_USER); |
| conf.set("proxyuser.foo.hosts", "localhost"); |
| return conf; |
| } |
| } |
| |
| @Test |
| public void testFallbackToPseudoDelegationTokenAuthenticator() |
| throws Exception { |
| final Server jetty = createJettyServer(); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| context.addFilter(new FilterHolder(PseudoDTAFilter.class), "/*", 0); |
| context.addServlet(new ServletHolder(UserServlet.class), "/bar"); |
| |
| try { |
| jetty.start(); |
| final URL url = new URL(getJettyURL() + "/foo/bar"); |
| |
| UserGroupInformation ugi = UserGroupInformation.createRemoteUser(FOO_USER); |
| ugi.doAs(new PrivilegedExceptionAction<Void>() { |
| @Override |
| public Void run() throws Exception { |
| DelegationTokenAuthenticatedURL.Token token = |
| new DelegationTokenAuthenticatedURL.Token(); |
| DelegationTokenAuthenticatedURL aUrl = |
| new DelegationTokenAuthenticatedURL(); |
| HttpURLConnection conn = aUrl.openConnection(url, token); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, |
| conn.getResponseCode()); |
| List<String> ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals(FOO_USER, ret.get(0)); |
| |
| aUrl.getDelegationToken(url, token, FOO_USER); |
| Assert.assertNotNull(token.getDelegationToken()); |
| Assert.assertEquals(new Text("token-kind"), |
| token.getDelegationToken().getKind()); |
| return null; |
| } |
| }); |
| } finally { |
| jetty.stop(); |
| } |
| } |
| |
| public static class KDTAFilter extends DelegationTokenAuthenticationFilter { |
| static String keytabFile; |
| |
| @Override |
| protected Properties getConfiguration(String configPrefix, |
| FilterConfig filterConfig) { |
| Properties conf = new Properties(); |
| conf.setProperty(AUTH_TYPE, |
| KerberosDelegationTokenAuthenticationHandler.class.getName()); |
| conf.setProperty(KerberosAuthenticationHandler.KEYTAB, keytabFile); |
| conf.setProperty(KerberosAuthenticationHandler.PRINCIPAL, |
| "HTTP/localhost"); |
| conf.setProperty(KerberosDelegationTokenAuthenticationHandler.TOKEN_KIND, |
| "token-kind"); |
| return conf; |
| } |
| |
| @Override |
| protected org.apache.hadoop.conf.Configuration getProxyuserConfiguration( |
| FilterConfig filterConfig) throws ServletException { |
| org.apache.hadoop.conf.Configuration conf = |
| new org.apache.hadoop.conf.Configuration(false); |
| conf.set("proxyuser.client.users", OK_USER); |
| conf.set("proxyuser.client.hosts", "127.0.0.1"); |
| return conf; |
| } |
| } |
| |
| private static class KerberosConfiguration extends Configuration { |
| private String principal; |
| private String keytab; |
| |
| public KerberosConfiguration(String principal, String keytab) { |
| this.principal = principal; |
| this.keytab = keytab; |
| } |
| |
| @Override |
| public AppConfigurationEntry[] getAppConfigurationEntry(String name) { |
| Map<String, String> options = new HashMap<String, String>(); |
| options.put("principal", principal); |
| options.put("keyTab", keytab); |
| options.put("useKeyTab", "true"); |
| options.put("storeKey", "true"); |
| options.put("doNotPrompt", "true"); |
| options.put("useTicketCache", "true"); |
| options.put("renewTGT", "true"); |
| options.put("refreshKrb5Config", "true"); |
| options.put("isInitiator", "true"); |
| String ticketCache = System.getenv("KRB5CCNAME"); |
| if (ticketCache != null) { |
| options.put("ticketCache", ticketCache); |
| } |
| options.put("debug", "true"); |
| |
| return new AppConfigurationEntry[]{ |
| new AppConfigurationEntry(KerberosUtil.getKrb5LoginModuleName(), |
| AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, |
| options),}; |
| } |
| } |
| |
| public static <T> T doAsKerberosUser(String principal, String keytab, |
| final Callable<T> callable) throws Exception { |
| LoginContext loginContext = null; |
| try { |
| Set<Principal> principals = new HashSet<Principal>(); |
| principals.add(new KerberosPrincipal(principal)); |
| Subject subject = new Subject(false, principals, new HashSet<Object>(), |
| new HashSet<Object>()); |
| loginContext = new LoginContext("", subject, null, |
| new KerberosConfiguration(principal, keytab)); |
| loginContext.login(); |
| subject = loginContext.getSubject(); |
| return Subject.doAs(subject, new PrivilegedExceptionAction<T>() { |
| @Override |
| public T run() throws Exception { |
| return callable.call(); |
| } |
| }); |
| } catch (PrivilegedActionException ex) { |
| throw ex.getException(); |
| } finally { |
| if (loginContext != null) { |
| loginContext.logout(); |
| } |
| } |
| } |
| |
| @Test |
| public void testKerberosDelegationTokenAuthenticator() throws Exception { |
| testKerberosDelegationTokenAuthenticator(false); |
| } |
| |
| @Test |
| public void testKerberosDelegationTokenAuthenticatorWithDoAs() |
| throws Exception { |
| testKerberosDelegationTokenAuthenticator(true); |
| } |
| |
| private void testKerberosDelegationTokenAuthenticator( |
| final boolean doAs) throws Exception { |
| final String doAsUser = doAs ? OK_USER : null; |
| |
| // setting hadoop security to kerberos |
| org.apache.hadoop.conf.Configuration conf = |
| new org.apache.hadoop.conf.Configuration(); |
| conf.set("hadoop.security.authentication", "kerberos"); |
| UserGroupInformation.setConfiguration(conf); |
| |
| File testDir = new File("target/" + UUID.randomUUID().toString()); |
| Assert.assertTrue(testDir.mkdirs()); |
| MiniKdc kdc = new MiniKdc(MiniKdc.createConf(), testDir); |
| final Server jetty = createJettyServer(); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| ((AbstractConnector)jetty.getConnectors()[0]).setResolveNames(true); |
| context.addFilter(new FilterHolder(KDTAFilter.class), "/*", 0); |
| context.addServlet(new ServletHolder(UserServlet.class), "/bar"); |
| try { |
| kdc.start(); |
| File keytabFile = new File(testDir, "test.keytab"); |
| kdc.createPrincipal(keytabFile, "client", "HTTP/localhost"); |
| KDTAFilter.keytabFile = keytabFile.getAbsolutePath(); |
| jetty.start(); |
| |
| final DelegationTokenAuthenticatedURL.Token token = |
| new DelegationTokenAuthenticatedURL.Token(); |
| final DelegationTokenAuthenticatedURL aUrl = |
| new DelegationTokenAuthenticatedURL(); |
| final URL url = new URL(getJettyURL() + "/foo/bar"); |
| |
| try { |
| aUrl.getDelegationToken(url, token, FOO_USER, doAsUser); |
| Assert.fail(); |
| } catch (AuthenticationException ex) { |
| Assert.assertTrue(ex.getMessage().contains("GSSException")); |
| } |
| |
| doAsKerberosUser("client", keytabFile.getAbsolutePath(), |
| new Callable<Void>() { |
| @Override |
| public Void call() throws Exception { |
| aUrl.getDelegationToken( |
| url, token, doAs ? doAsUser : "client", doAsUser); |
| Assert.assertNotNull(token.getDelegationToken()); |
| Assert.assertEquals(new Text("token-kind"), |
| token.getDelegationToken().getKind()); |
| // Make sure the token belongs to the right owner |
| ByteArrayInputStream buf = new ByteArrayInputStream( |
| token.getDelegationToken().getIdentifier()); |
| DataInputStream dis = new DataInputStream(buf); |
| DelegationTokenIdentifier id = |
| new DelegationTokenIdentifier(new Text("token-kind")); |
| id.readFields(dis); |
| dis.close(); |
| Assert.assertEquals( |
| doAs ? new Text(OK_USER) : new Text("client"), id.getOwner()); |
| if (doAs) { |
| Assert.assertEquals(new Text("client"), id.getRealUser()); |
| } |
| |
| aUrl.renewDelegationToken(url, token, doAsUser); |
| Assert.assertNotNull(token.getDelegationToken()); |
| |
| aUrl.getDelegationToken(url, token, FOO_USER, doAsUser); |
| Assert.assertNotNull(token.getDelegationToken()); |
| |
| try { |
| aUrl.renewDelegationToken(url, token, doAsUser); |
| Assert.fail(); |
| } catch (Exception ex) { |
| Assert.assertTrue(ex.getMessage().contains("403")); |
| } |
| |
| aUrl.getDelegationToken(url, token, FOO_USER, doAsUser); |
| |
| aUrl.cancelDelegationToken(url, token, doAsUser); |
| Assert.assertNull(token.getDelegationToken()); |
| |
| return null; |
| } |
| }); |
| } finally { |
| jetty.stop(); |
| kdc.stop(); |
| } |
| } |
| |
| @Test |
| public void testProxyUser() throws Exception { |
| final Server jetty = createJettyServer(); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| context.addFilter(new FilterHolder(PseudoDTAFilter.class), "/*", 0); |
| context.addServlet(new ServletHolder(UserServlet.class), "/bar"); |
| |
| try { |
| jetty.start(); |
| final URL url = new URL(getJettyURL() + "/foo/bar"); |
| |
| // proxyuser using raw HTTP, verifying doAs is case insensitive |
| String strUrl = String.format("%s?user.name=%s&doas=%s", |
| url.toExternalForm(), FOO_USER, OK_USER); |
| HttpURLConnection conn = |
| (HttpURLConnection) new URL(strUrl).openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| List<String> ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals(OK_USER, ret.get(0)); |
| strUrl = String.format("%s?user.name=%s&DOAS=%s", url.toExternalForm(), |
| FOO_USER, OK_USER); |
| conn = (HttpURLConnection) new URL(strUrl).openConnection(); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); |
| ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals(OK_USER, ret.get(0)); |
| |
| UserGroupInformation ugi = UserGroupInformation.createRemoteUser(FOO_USER); |
| ugi.doAs(new PrivilegedExceptionAction<Void>() { |
| @Override |
| public Void run() throws Exception { |
| DelegationTokenAuthenticatedURL.Token token = |
| new DelegationTokenAuthenticatedURL.Token(); |
| DelegationTokenAuthenticatedURL aUrl = |
| new DelegationTokenAuthenticatedURL(); |
| |
| // proxyuser using authentication handler authentication |
| HttpURLConnection conn = aUrl.openConnection(url, token, OK_USER); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, |
| conn.getResponseCode()); |
| List<String> ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals(OK_USER, ret.get(0)); |
| |
| // unauthorized proxy user using authentication handler authentication |
| conn = aUrl.openConnection(url, token, FAIL_USER); |
| Assert.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, |
| conn.getResponseCode()); |
| |
| // proxy using delegation token authentication |
| aUrl.getDelegationToken(url, token, FOO_USER); |
| |
| UserGroupInformation ugi = UserGroupInformation.getCurrentUser(); |
| ugi.addToken(token.getDelegationToken()); |
| token = new DelegationTokenAuthenticatedURL.Token(); |
| |
| // requests using delegation token as auth do not honor doAs |
| conn = aUrl.openConnection(url, token, OK_USER); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, |
| conn.getResponseCode()); |
| ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals(FOO_USER, ret.get(0)); |
| |
| return null; |
| } |
| }); |
| } finally { |
| jetty.stop(); |
| } |
| } |
| |
| |
| public static class UGIServlet extends HttpServlet { |
| |
| @Override |
| protected void doGet(HttpServletRequest req, HttpServletResponse resp) |
| throws ServletException, IOException { |
| UserGroupInformation ugi = HttpUserGroupInformation.get(); |
| if (ugi != null) { |
| String ret = "remoteuser=" + req.getRemoteUser() + ":ugi=" + |
| ugi.getShortUserName(); |
| if (ugi.getAuthenticationMethod() == |
| UserGroupInformation.AuthenticationMethod.PROXY) { |
| ret = "realugi=" + ugi.getRealUser().getShortUserName() + ":" + ret; |
| } |
| resp.setStatus(HttpServletResponse.SC_OK); |
| resp.getWriter().write(ret); |
| } else { |
| resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); |
| } |
| } |
| } |
| |
| @Test |
| public void testHttpUGI() throws Exception { |
| final Server jetty = createJettyServer(); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| context.addFilter(new FilterHolder(PseudoDTAFilter.class), "/*", 0); |
| context.addServlet(new ServletHolder(UGIServlet.class), "/bar"); |
| |
| try { |
| jetty.start(); |
| final URL url = new URL(getJettyURL() + "/foo/bar"); |
| |
| UserGroupInformation ugi = UserGroupInformation.createRemoteUser(FOO_USER); |
| ugi.doAs(new PrivilegedExceptionAction<Void>() { |
| @Override |
| public Void run() throws Exception { |
| DelegationTokenAuthenticatedURL.Token token = |
| new DelegationTokenAuthenticatedURL.Token(); |
| DelegationTokenAuthenticatedURL aUrl = |
| new DelegationTokenAuthenticatedURL(); |
| |
| // user foo |
| HttpURLConnection conn = aUrl.openConnection(url, token); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, |
| conn.getResponseCode()); |
| List<String> ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals("remoteuser=" + FOO_USER+ ":ugi=" + FOO_USER, |
| ret.get(0)); |
| |
| // user ok-user via proxyuser foo |
| conn = aUrl.openConnection(url, token, OK_USER); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, |
| conn.getResponseCode()); |
| ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals("realugi=" + FOO_USER +":remoteuser=" + OK_USER + |
| ":ugi=" + OK_USER, ret.get(0)); |
| |
| return null; |
| } |
| }); |
| } finally { |
| jetty.stop(); |
| } |
| } |
| |
| public static class IpAddressBasedPseudoDTAFilter extends PseudoDTAFilter { |
| @Override |
| protected org.apache.hadoop.conf.Configuration getProxyuserConfiguration |
| (FilterConfig filterConfig) throws ServletException { |
| org.apache.hadoop.conf.Configuration configuration = super |
| .getProxyuserConfiguration(filterConfig); |
| configuration.set("proxyuser.foo.hosts", "127.0.0.1"); |
| return configuration; |
| } |
| } |
| |
| @Test |
| public void testIpaddressCheck() throws Exception { |
| final Server jetty = createJettyServer(); |
| ((AbstractConnector)jetty.getConnectors()[0]).setResolveNames(true); |
| Context context = new Context(); |
| context.setContextPath("/foo"); |
| jetty.setHandler(context); |
| |
| context.addFilter(new FilterHolder(IpAddressBasedPseudoDTAFilter.class), "/*", 0); |
| context.addServlet(new ServletHolder(UGIServlet.class), "/bar"); |
| |
| try { |
| jetty.start(); |
| final URL url = new URL(getJettyURL() + "/foo/bar"); |
| |
| UserGroupInformation ugi = UserGroupInformation.createRemoteUser(FOO_USER); |
| ugi.doAs(new PrivilegedExceptionAction<Void>() { |
| @Override |
| public Void run() throws Exception { |
| DelegationTokenAuthenticatedURL.Token token = |
| new DelegationTokenAuthenticatedURL.Token(); |
| DelegationTokenAuthenticatedURL aUrl = |
| new DelegationTokenAuthenticatedURL(); |
| |
| // user ok-user via proxyuser foo |
| HttpURLConnection conn = aUrl.openConnection(url, token, OK_USER); |
| Assert.assertEquals(HttpURLConnection.HTTP_OK, |
| conn.getResponseCode()); |
| List<String> ret = IOUtils.readLines(conn.getInputStream()); |
| Assert.assertEquals(1, ret.size()); |
| Assert.assertEquals("realugi=" + FOO_USER +":remoteuser=" + OK_USER + |
| ":ugi=" + OK_USER, ret.get(0)); |
| |
| return null; |
| } |
| }); |
| } finally { |
| jetty.stop(); |
| } |
| } |
| |
| } |