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.Assert.assertEquals; 021 022import java.io.ByteArrayOutputStream; 023import java.io.File; 024import java.io.IOException; 025import java.io.InputStream; 026import java.net.URI; 027import java.net.URL; 028import java.security.GeneralSecurityException; 029import javax.net.ssl.HttpsURLConnection; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.fs.FileUtil; 032import org.apache.hadoop.hbase.HBaseClassTestRule; 033import org.apache.hadoop.hbase.HBaseCommonTestingUtility; 034import org.apache.hadoop.hbase.HBaseConfiguration; 035import org.apache.hadoop.hbase.http.ssl.KeyStoreTestUtil; 036import org.apache.hadoop.hbase.testclassification.MediumTests; 037import org.apache.hadoop.hbase.testclassification.MiscTests; 038import org.apache.hadoop.io.IOUtils; 039import org.apache.hadoop.net.NetUtils; 040import org.apache.hadoop.security.ssl.SSLFactory; 041import org.junit.AfterClass; 042import org.junit.BeforeClass; 043import org.junit.ClassRule; 044import org.junit.Test; 045import org.junit.experimental.categories.Category; 046import org.slf4j.Logger; 047import org.slf4j.LoggerFactory; 048 049/** 050 * This testcase issues SSL certificates configures the HttpServer to serve HTTPS using the created 051 * certficates and calls an echo servlet using the corresponding HTTPS URL. 052 */ 053@Category({ MiscTests.class, MediumTests.class }) 054public class TestSSLHttpServer extends HttpServerFunctionalTest { 055 056 @ClassRule 057 public static final HBaseClassTestRule CLASS_RULE = 058 HBaseClassTestRule.forClass(TestSSLHttpServer.class); 059 060 private static final Logger LOG = LoggerFactory.getLogger(TestSSLHttpServer.class); 061 private static Configuration serverConf; 062 private static HttpServer server; 063 private static URL baseUrl; 064 private static File keystoresDir; 065 private static String sslConfDir; 066 private static SSLFactory clientSslFactory; 067 private static HBaseCommonTestingUtility HTU; 068 069 @BeforeClass 070 public static void setup() throws Exception { 071 072 HTU = new HBaseCommonTestingUtility(); 073 serverConf = HTU.getConfiguration(); 074 075 serverConf.setInt(HttpServer.HTTP_MAX_THREADS, TestHttpServer.MAX_THREADS); 076 serverConf.setBoolean(ServerConfigurationKeys.HBASE_SSL_ENABLED_KEY, true); 077 078 keystoresDir = new File(HTU.getDataTestDir("keystore").toString()); 079 keystoresDir.mkdirs(); 080 081 sslConfDir = KeyStoreTestUtil.getClasspathDir(TestSSLHttpServer.class); 082 083 KeyStoreTestUtil.setupSSLConfig(keystoresDir.getAbsolutePath(), sslConfDir, serverConf, false); 084 Configuration clientConf = new Configuration(false); 085 clientConf.addResource(serverConf.get(SSLFactory.SSL_CLIENT_CONF_KEY)); 086 serverConf.addResource(serverConf.get(SSLFactory.SSL_SERVER_CONF_KEY)); 087 clientConf.set(SSLFactory.SSL_CLIENT_CONF_KEY, serverConf.get(SSLFactory.SSL_CLIENT_CONF_KEY)); 088 089 clientSslFactory = new SSLFactory(SSLFactory.Mode.CLIENT, clientConf); 090 clientSslFactory.init(); 091 092 server = new HttpServer.Builder().setName("test").addEndpoint(new URI("https://localhost")) 093 .setConf(serverConf) 094 .keyPassword( 095 HBaseConfiguration.getPassword(serverConf, "ssl.server.keystore.keypassword", null)) 096 .keyStore(serverConf.get("ssl.server.keystore.location"), 097 HBaseConfiguration.getPassword(serverConf, "ssl.server.keystore.password", null), 098 clientConf.get("ssl.server.keystore.type", "jks")) 099 .trustStore(serverConf.get("ssl.server.truststore.location"), 100 HBaseConfiguration.getPassword(serverConf, "ssl.server.truststore.password", null), 101 serverConf.get("ssl.server.truststore.type", "jks")) 102 .build(); 103 server.addUnprivilegedServlet("echo", "/echo", TestHttpServer.EchoServlet.class); 104 server.start(); 105 baseUrl = new URL("https://" + NetUtils.getHostPortString(server.getConnectorAddress(0))); 106 LOG.info("HTTP server started: " + baseUrl); 107 } 108 109 @AfterClass 110 public static void cleanup() throws Exception { 111 server.stop(); 112 FileUtil.fullyDelete(new File(HTU.getDataTestDir().toString())); 113 KeyStoreTestUtil.cleanupSSLConfig(serverConf); 114 clientSslFactory.destroy(); 115 } 116 117 @Test 118 public void testEcho() throws Exception { 119 assertEquals("a:b\nc:d\n", readOut(new URL(baseUrl, "/echo?a=b&c=d"))); 120 assertEquals("a:b\nc<:d\ne:>\n", readOut(new URL(baseUrl, "/echo?a=b&c<=d&e=>"))); 121 } 122 123 @Test 124 public void testSecurityHeaders() throws IOException, GeneralSecurityException { 125 HttpsURLConnection conn = (HttpsURLConnection) baseUrl.openConnection(); 126 conn.setSSLSocketFactory(clientSslFactory.createSSLSocketFactory()); 127 assertEquals(HttpsURLConnection.HTTP_OK, conn.getResponseCode()); 128 assertEquals("max-age=63072000;includeSubDomains;preload", 129 conn.getHeaderField("Strict-Transport-Security")); 130 assertEquals("default-src https: data: 'unsafe-inline' 'unsafe-eval'", 131 conn.getHeaderField("Content-Security-Policy")); 132 } 133 134 private static String readOut(URL url) throws Exception { 135 HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); 136 conn.setSSLSocketFactory(clientSslFactory.createSSLSocketFactory()); 137 InputStream in = conn.getInputStream(); 138 ByteArrayOutputStream out = new ByteArrayOutputStream(); 139 IOUtils.copyBytes(in, out, 1024); 140 return out.toString(); 141 } 142 143}