001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.http;
019
020import static org.junit.jupiter.api.Assertions.assertTrue;
021
022import java.io.File;
023import java.io.IOException;
024import java.net.HttpCookie;
025import java.net.HttpURLConnection;
026import java.net.URI;
027import java.net.URL;
028import java.security.GeneralSecurityException;
029import java.util.List;
030import javax.net.ssl.HttpsURLConnection;
031import javax.servlet.Filter;
032import javax.servlet.FilterChain;
033import javax.servlet.FilterConfig;
034import javax.servlet.ServletException;
035import javax.servlet.ServletRequest;
036import javax.servlet.ServletResponse;
037import javax.servlet.http.HttpServletResponse;
038import org.apache.hadoop.conf.Configuration;
039import org.apache.hadoop.fs.FileUtil;
040import org.apache.hadoop.hbase.testclassification.MiscTests;
041import org.apache.hadoop.hbase.testclassification.SmallTests;
042import org.apache.hadoop.net.NetUtils;
043import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
044import org.apache.hadoop.security.ssl.KeyStoreTestUtil;
045import org.apache.hadoop.security.ssl.SSLFactory;
046import org.junit.jupiter.api.AfterAll;
047import org.junit.jupiter.api.BeforeAll;
048import org.junit.jupiter.api.Tag;
049import org.junit.jupiter.api.Test;
050
051@Tag(MiscTests.TAG)
052@Tag(SmallTests.TAG)
053public class TestHttpCookieFlag {
054
055  private static final String BASEDIR = System.getProperty("test.build.dir", "target/test-dir")
056    + "/" + org.apache.hadoop.hbase.http.TestHttpCookieFlag.class.getSimpleName();
057  private static String keystoresDir;
058  private static String sslConfDir;
059  private static SSLFactory clientSslFactory;
060  private static HttpServer server;
061
062  public static class DummyAuthenticationFilter implements Filter {
063
064    @Override
065    public void init(FilterConfig filterConfig) throws ServletException {
066    }
067
068    @Override
069    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
070      throws IOException, ServletException {
071      HttpServletResponse resp = (HttpServletResponse) response;
072      boolean isHttps = "https".equals(request.getScheme());
073      AuthenticationFilter.createAuthCookie(resp, "token", null, null, -1, true, isHttps);
074      chain.doFilter(request, resp);
075    }
076
077    @Override
078    public void destroy() {
079    }
080  }
081
082  public static class DummyFilterInitializer extends FilterInitializer {
083    @Override
084    public void initFilter(FilterContainer container, Configuration conf) {
085      container.addFilter("DummyAuth", DummyAuthenticationFilter.class.getName(), null);
086    }
087  }
088
089  @BeforeAll
090  public static void setUp() throws Exception {
091    Configuration conf = new Configuration();
092    conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, DummyFilterInitializer.class.getName());
093    conf.setInt("hbase.http.max.threads", 19); /* acceptors=2 + selectors=16 + request=1 */
094    System.setProperty("hadoop.log.dir", BASEDIR); /* needed for /logs */
095
096    File base = new File(BASEDIR);
097    FileUtil.fullyDelete(base);
098    base.mkdirs();
099    keystoresDir = new File(BASEDIR).getAbsolutePath();
100    sslConfDir = KeyStoreTestUtil.getClasspathDir(TestSSLHttpServer.class);
101
102    KeyStoreTestUtil.setupSSLConfig(keystoresDir, sslConfDir, conf, false);
103    Configuration sslConf = KeyStoreTestUtil.getSslConfig();
104
105    clientSslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, sslConf);
106    clientSslFactory.init();
107
108    server = new HttpServer.Builder().setName("test").addEndpoint(new URI("http://localhost"))
109      .addEndpoint(new URI("https://localhost")).setConf(conf)
110      .keyPassword(sslConf.get("ssl.server.keystore.keypassword"))
111      .keyStore(sslConf.get("ssl.server.keystore.location"),
112        sslConf.get("ssl.server.keystore.password"), sslConf.get("ssl.server.keystore.type", "jks"))
113      .trustStore(sslConf.get("ssl.server.truststore.location"),
114        sslConf.get("ssl.server.truststore.password"),
115        sslConf.get("ssl.server.truststore.type", "jks"))
116      .build();
117    server.addPrivilegedServlet("echo", "/echo", TestHttpServer.EchoServlet.class);
118    server.start();
119  }
120
121  @Test
122  public void testHttpCookie() throws IOException {
123    URL base = new URL("http://" + NetUtils.getHostPortString(server.getConnectorAddress(0)));
124    HttpURLConnection conn = (HttpURLConnection) new URL(base, "/echo").openConnection();
125
126    String header = conn.getHeaderField("Set-Cookie");
127    assertTrue(header != null);
128    List<HttpCookie> cookies = HttpCookie.parse(header);
129    assertTrue(!cookies.isEmpty());
130    assertTrue(header.contains("; HttpOnly"));
131    assertTrue("token".equals(cookies.get(0).getValue()));
132  }
133
134  @Test
135  public void testHttpsCookie() throws IOException, GeneralSecurityException {
136    URL base = new URL("https://" + NetUtils.getHostPortString(server.getConnectorAddress(1)));
137    HttpsURLConnection conn = (HttpsURLConnection) new URL(base, "/echo").openConnection();
138    conn.setSSLSocketFactory(clientSslFactory.createSSLSocketFactory());
139
140    String header = conn.getHeaderField("Set-Cookie");
141    assertTrue(header != null);
142
143    List<HttpCookie> cookies = HttpCookie.parse(header);
144    assertTrue(!cookies.isEmpty());
145    assertTrue(header.contains("; HttpOnly"));
146    assertTrue(cookies.get(0).getSecure());
147    assertTrue("token".equals(cookies.get(0).getValue()));
148  }
149
150  @Test
151  public void testHttpsCookieDefaultServlets() throws Exception {
152    HttpsURLConnection conn = null;
153
154    URL base =
155      new URL("https://" + NetUtils.getHostPortString(server.getConnectorAddress(1)) + "/");
156
157    for (String servlet : new String[] { "static", "stacks", "logLevel", "jmx", "logs" }) {
158      conn = (HttpsURLConnection) new URL(base, "/" + servlet).openConnection();
159      conn.setSSLSocketFactory(clientSslFactory.createSSLSocketFactory());
160
161      String header = conn.getHeaderField("Set-Cookie");
162      assertTrue(header != null);
163      List<HttpCookie> cookies = HttpCookie.parse(header);
164      assertTrue(!cookies.isEmpty());
165      assertTrue(header.contains("; HttpOnly"));
166      assertTrue(cookies.get(0).getSecure());
167      assertTrue("token".equals(cookies.get(0).getValue()));
168    }
169  }
170
171  @AfterAll
172  public static void cleanup() throws Exception {
173    server.stop();
174    FileUtil.fullyDelete(new File(BASEDIR));
175    KeyStoreTestUtil.cleanupSSLConfig(keystoresDir, sslConfDir);
176    clientSslFactory.destroy();
177  }
178}