From 41841a17c57432200dd5319bfd970c15df96c63f Mon Sep 17 00:00:00 2001 From: Esteban Gutierrez Date: Wed, 29 Nov 2017 14:36:26 -0600 Subject: [PATCH] HBASE-19352 Port HADOOP-10379: Protect authentication cookies with the HttpOnly and Secure flags --- .../org/apache/hadoop/hbase/http/HttpServer.java | 30 +++- .../hadoop/hbase/http/TestHttpCookieFlag.java | 154 +++++++++++++++++++++ 2 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpCookieFlag.java diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java index c2b5944ba2..baf0339f9a 100644 --- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java +++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java @@ -75,11 +75,14 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.SessionManager; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.handler.RequestLogHandler; +import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.server.session.AbstractSessionManager; import org.eclipse.jetty.servlet.FilterMapping; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.FilterHolder; @@ -131,7 +134,6 @@ public class HttpServer implements FilterContainer { "signature.secret.file"; public static final String HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_KEY = HTTP_AUTHENTICATION_PREFIX + HTTP_AUTHENTICATION_SIGNATURE_SECRET_FILE_SUFFIX; - // The ServletContext attribute where the daemon Configuration // gets stored. public static final String CONF_CONTEXT_ATTRIBUTE = "hbase.conf"; @@ -541,6 +543,13 @@ public class HttpServer implements FilterContainer { HandlerCollection handlerCollection = new HandlerCollection(); + SessionManager sm = webAppContext.getSessionHandler().getSessionManager(); + if (sm instanceof AbstractSessionManager) { + AbstractSessionManager asm = (AbstractSessionManager)sm; + asm.setHttpOnly(true); + asm.getSessionCookieConfig().setSecure(true); + } + ContextHandlerCollection contexts = new ContextHandlerCollection(); RequestLog requestLog = HttpRequestLog.getRequestLog(name); @@ -647,6 +656,14 @@ public class HttpServer implements FilterContainer { "org.mortbay.jetty.servlet.Default.aliases", "true"); } logContext.setDisplayName("logs"); + SessionHandler handler = new SessionHandler(); + SessionManager sm = handler.getSessionManager(); + if (sm instanceof AbstractSessionManager) { + AbstractSessionManager asm = (AbstractSessionManager) sm; + asm.setHttpOnly(true); + asm.getSessionCookieConfig().setSecure(true); + } + logContext.setSessionHandler(handler); setContextAttributes(logContext, conf); addNoCacheFilter(webAppContext); defaultContexts.put(logContext, true); @@ -656,6 +673,17 @@ public class HttpServer implements FilterContainer { staticContext.setResourceBase(appDir + "/static"); staticContext.addServlet(DefaultServlet.class, "/*"); staticContext.setDisplayName("static"); + @SuppressWarnings("unchecked") + Map params = staticContext.getInitParams(); + params.put("org.eclipse.jetty.servlet.Default.dirAllowed", "false"); + SessionHandler handler = new SessionHandler(); + SessionManager sm = handler.getSessionManager(); + if (sm instanceof AbstractSessionManager) { + AbstractSessionManager asm = (AbstractSessionManager) sm; + asm.setHttpOnly(true); + asm.getSessionCookieConfig().setSecure(true); + } + staticContext.setSessionHandler(handler); setContextAttributes(staticContext, conf); defaultContexts.put(staticContext, true); } diff --git a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpCookieFlag.java b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpCookieFlag.java new file mode 100644 index 0000000000..5cde8b1c88 --- /dev/null +++ b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpCookieFlag.java @@ -0,0 +1,154 @@ +/** + * Licensed 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. See accompanying LICENSE file. + */ +package org.apache.hadoop.hbase.http; + +import org.junit.Assert; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.net.NetUtils; +import org.apache.hadoop.security.authentication.server.AuthenticationFilter; +import org.apache.hadoop.security.ssl.KeyStoreTestUtil; +import org.apache.hadoop.security.ssl.SSLFactory; +import org.apache.hadoop.test.GenericTestUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.net.ssl.HttpsURLConnection; +import javax.servlet.*; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.security.GeneralSecurityException; +import java.net.HttpCookie; +import java.util.List; + +public class TestHttpCookieFlag { + private static final String BASEDIR = System.getProperty("test.build.dir", + "target/test-dir") + "/" + org.apache.hadoop.hbase.http.TestHttpCookieFlag.class.getSimpleName(); + private static String keystoresDir; + private static String sslConfDir; + private static SSLFactory clientSslFactory; + private static HttpServer server; + + public static class DummyAuthenticationFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, + ServletException { + HttpServletResponse resp = (HttpServletResponse) response; + boolean isHttps = "https".equals(request.getScheme()); + AuthenticationFilter.createAuthCookie(resp, "token", null, null, -1, + true, isHttps); + chain.doFilter(request, resp); + } + + @Override + public void destroy() { + } + } + public static class DummyFilterInitializer extends FilterInitializer { + @Override + public void initFilter(FilterContainer container, Configuration conf) { + container.addFilter("DummyAuth", DummyAuthenticationFilter.class + .getName(), null); + } + } + + @BeforeClass + public static void setUp() throws Exception { + Configuration conf = new Configuration(); + conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, + DummyFilterInitializer.class.getName()); + + File base = new File(BASEDIR); + FileUtil.fullyDelete(base); + base.mkdirs(); + keystoresDir = new File(BASEDIR).getAbsolutePath(); + sslConfDir = KeyStoreTestUtil.getClasspathDir(TestSSLHttpServer.class); + + KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfDir, conf, false); + Configuration sslConf = KeyStoreTestUtil.getSslConfig(); + + clientSslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, sslConf); + clientSslFactory.init(); + + server = new HttpServer.Builder() + .setName("test") + .addEndpoint(new URI("http://localhost")) + .addEndpoint(new URI("https://localhost")) + .setConf(conf) + .keyPassword(sslConf.get("ssl.server.keystore.keypassword")) + .keyStore(sslConf.get("ssl.server.keystore.location"), + sslConf.get("ssl.server.keystore.password"), + sslConf.get("ssl.server.keystore.type", "jks")) + .trustStore(sslConf.get("ssl.server.truststore.location"), + sslConf.get("ssl.server.truststore.password"), + sslConf.get("ssl.server.truststore.type", "jks")) + //.excludeCiphers( + // sslConf.get("ssl.server.exclude.cipher.list")) + .build(); + server.addServlet("echo", "/echo", TestHttpServer.EchoServlet.class); + server.start(); + } + + @Test + public void testHttpCookie() throws IOException { + URL base = new URL("http://" + NetUtils.getHostPortString(server + .getConnectorAddress(0))); + HttpURLConnection conn = (HttpURLConnection) new URL(base, + "/echo").openConnection(); + + String header = conn.getHeaderField("Set-Cookie"); + Assert.assertTrue(header != null); + List cookies = HttpCookie.parse(header); + Assert.assertTrue(!cookies.isEmpty()); + Assert.assertTrue(header.contains("; HttpOnly")); + Assert.assertTrue("token".equals(cookies.get(0).getValue())); + } + + @Test + public void testHttpsCookie() throws IOException, GeneralSecurityException { + URL base = new URL("https://" + NetUtils.getHostPortString(server + .getConnectorAddress(1))); + HttpsURLConnection conn = (HttpsURLConnection) new URL(base, + "/echo").openConnection(); + conn.setSSLSocketFactory(clientSslFactory.createSSLSocketFactory()); + + String header = conn.getHeaderField("Set-Cookie"); + Assert.assertTrue(header != null); + + List cookies = HttpCookie.parse(header); + Assert.assertTrue(!cookies.isEmpty()); + Assert.assertTrue(header.contains("; HttpOnly")); + Assert.assertTrue(cookies.get(0).getSecure()); + Assert.assertTrue("token".equals(cookies.get(0).getValue())); + } + + @AfterClass + public static void cleanup() throws Exception { + server.stop(); + FileUtil.fullyDelete(new File(BASEDIR)); + KeyStoreTestUtil.cleanupSSLConfig(keystoresDir, sslConfDir); + clientSslFactory.destroy(); + } +} -- 2.15.1