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.thrift; 019 020import static org.apache.hadoop.hbase.thrift.TestThriftServerCmdLine.createBoundServer; 021import static org.junit.jupiter.api.Assertions.assertEquals; 022 023import java.io.BufferedInputStream; 024import java.io.File; 025import java.io.IOException; 026import java.io.InputStream; 027import java.lang.reflect.Method; 028import java.net.HttpURLConnection; 029import java.nio.file.Files; 030import java.security.KeyPair; 031import java.security.KeyStore; 032import java.security.cert.X509Certificate; 033import javax.net.ssl.SSLContext; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.hbase.HBaseTestingUtil; 036import org.apache.hadoop.hbase.HConstants; 037import org.apache.hadoop.hbase.testclassification.ClientTests; 038import org.apache.hadoop.hbase.testclassification.LargeTests; 039import org.apache.hadoop.hbase.thrift.generated.Hbase; 040import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 041import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper; 042import org.apache.hadoop.hbase.util.IncrementingEnvironmentEdge; 043import org.apache.hadoop.hbase.util.TableDescriptorChecker; 044import org.apache.hadoop.security.ssl.KeyStoreTestUtil; 045import org.apache.http.client.methods.CloseableHttpResponse; 046import org.apache.http.client.methods.HttpPost; 047import org.apache.http.entity.ByteArrayEntity; 048import org.apache.http.impl.client.CloseableHttpClient; 049import org.apache.http.impl.client.HttpClientBuilder; 050import org.apache.http.impl.client.HttpClients; 051import org.apache.http.ssl.SSLContexts; 052import org.junit.jupiter.api.AfterAll; 053import org.junit.jupiter.api.AfterEach; 054import org.junit.jupiter.api.BeforeAll; 055import org.junit.jupiter.api.BeforeEach; 056import org.junit.jupiter.api.Tag; 057import org.junit.jupiter.api.Test; 058import org.slf4j.Logger; 059import org.slf4j.LoggerFactory; 060 061import org.apache.hbase.thirdparty.org.apache.thrift.protocol.TBinaryProtocol; 062import org.apache.hbase.thirdparty.org.apache.thrift.protocol.TProtocol; 063import org.apache.hbase.thirdparty.org.apache.thrift.transport.TMemoryBuffer; 064 065@Tag(ClientTests.TAG) 066@Tag(LargeTests.TAG) 067public class TestThriftHttpServerSSL { 068 069 private static final Logger LOG = LoggerFactory.getLogger(TestThriftHttpServerSSL.class); 070 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 071 private static final String KEY_STORE_PASSWORD = "myKSPassword"; 072 private static final String TRUST_STORE_PASSWORD = "myTSPassword"; 073 074 private File keyDir; 075 private HttpClientBuilder httpClientBuilder; 076 private ThriftServerRunner tsr; 077 private HttpPost httpPost = null; 078 079 @BeforeAll 080 public static void setUpBeforeClass() throws Exception { 081 TEST_UTIL.getConfiguration().setBoolean(Constants.USE_HTTP_CONF_KEY, true); 082 TEST_UTIL.getConfiguration().setBoolean(TableDescriptorChecker.TABLE_SANITY_CHECKS, false); 083 TEST_UTIL.startMiniCluster(); 084 // ensure that server time increments every time we do an operation, otherwise 085 // successive puts having the same timestamp will override each other 086 EnvironmentEdgeManagerTestHelper.injectEdge(new IncrementingEnvironmentEdge()); 087 } 088 089 @AfterAll 090 public static void tearDownAfterClass() throws Exception { 091 TEST_UTIL.shutdownMiniCluster(); 092 EnvironmentEdgeManager.reset(); 093 } 094 095 @BeforeEach 096 public void setUp() throws Exception { 097 initializeAlgorithmId(); 098 keyDir = initKeystoreDir(); 099 keyDir.deleteOnExit(); 100 KeyPair keyPair = KeyStoreTestUtil.generateKeyPair("RSA"); 101 102 X509Certificate serverCertificate = 103 KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", keyPair, 30, "SHA1withRSA"); 104 105 generateTrustStore(serverCertificate); 106 generateKeyStore(keyPair, serverCertificate); 107 108 Configuration conf = new Configuration(TEST_UTIL.getConfiguration()); 109 conf.setBoolean(Constants.THRIFT_SSL_ENABLED_KEY, true); 110 conf.set(Constants.THRIFT_SSL_KEYSTORE_STORE_KEY, getKeystoreFilePath()); 111 conf.set(Constants.THRIFT_SSL_KEYSTORE_PASSWORD_KEY, KEY_STORE_PASSWORD); 112 conf.set(Constants.THRIFT_SSL_KEYSTORE_KEYPASSWORD_KEY, KEY_STORE_PASSWORD); 113 114 tsr = createBoundServer(() -> new ThriftServer(conf)); 115 String url = "https://" + HConstants.LOCALHOST + ":" + tsr.getThriftServer().listenPort; 116 117 KeyStore trustStore; 118 trustStore = KeyStore.getInstance("JKS"); 119 try (InputStream inputStream = 120 new BufferedInputStream(Files.newInputStream(new File(getTruststoreFilePath()).toPath()))) { 121 trustStore.load(inputStream, TRUST_STORE_PASSWORD.toCharArray()); 122 } 123 124 httpClientBuilder = HttpClients.custom(); 125 SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, null).build(); 126 httpClientBuilder.setSSLContext(sslcontext); 127 128 httpPost = new HttpPost(url); 129 httpPost.setHeader("Content-Type", "application/x-thrift"); 130 httpPost.setHeader("Accept", "application/x-thrift"); 131 httpPost.setHeader("User-Agent", "Java/THttpClient/HC"); 132 } 133 134 @AfterEach 135 public void tearDown() throws IOException { 136 if (httpPost != null) { 137 httpPost.releaseConnection(); 138 } 139 if (tsr != null) { 140 tsr.close(); 141 } 142 } 143 144 @Test 145 public void testSecurityHeaders() throws Exception { 146 try (CloseableHttpClient httpClient = httpClientBuilder.build()) { 147 TMemoryBuffer memoryBuffer = new TMemoryBuffer(100); 148 TProtocol prot = new TBinaryProtocol(memoryBuffer); 149 Hbase.Client client = new Hbase.Client(prot); 150 client.send_getClusterId(); 151 152 httpPost.setEntity(new ByteArrayEntity(memoryBuffer.getArray())); 153 CloseableHttpResponse httpResponse = httpClient.execute(httpPost); 154 155 assertEquals(HttpURLConnection.HTTP_OK, httpResponse.getStatusLine().getStatusCode()); 156 assertEquals("DENY", httpResponse.getFirstHeader("X-Frame-Options").getValue()); 157 158 assertEquals("nosniff", httpResponse.getFirstHeader("X-Content-Type-Options").getValue()); 159 assertEquals("1; mode=block", httpResponse.getFirstHeader("X-XSS-Protection").getValue()); 160 161 assertEquals("default-src https: data: 'unsafe-inline' 'unsafe-eval'", 162 httpResponse.getFirstHeader("Content-Security-Policy").getValue()); 163 assertEquals("max-age=63072000;includeSubDomains;preload", 164 httpResponse.getFirstHeader("Strict-Transport-Security").getValue()); 165 } 166 } 167 168 // Workaround for jdk8 292 bug. See https://github.com/bcgit/bc-java/issues/941 169 // Below is a workaround described in above URL. Issue fingered first in comments in 170 // HBASE-25920 Support Hadoop 3.3.1 171 private static void initializeAlgorithmId() { 172 try { 173 Class<?> algoId = Class.forName("sun.security.x509.AlgorithmId"); 174 Method method = algoId.getMethod("get", String.class); 175 method.setAccessible(true); 176 method.invoke(null, "PBEWithSHA1AndDESede"); 177 } catch (Exception e) { 178 LOG.warn("failed to initialize AlgorithmId", e); 179 } 180 } 181 182 private File initKeystoreDir() { 183 String dataTestDir = TEST_UTIL.getDataTestDir().toString(); 184 File keystoreDir = new File(dataTestDir, TestThriftHttpServer.class.getSimpleName() + "_keys"); 185 keystoreDir.mkdirs(); 186 return keystoreDir; 187 } 188 189 private void generateKeyStore(KeyPair keyPair, X509Certificate serverCertificate) 190 throws Exception { 191 String keyStorePath = getKeystoreFilePath(); 192 KeyStoreTestUtil.createKeyStore(keyStorePath, KEY_STORE_PASSWORD, KEY_STORE_PASSWORD, 193 "serverKS", keyPair.getPrivate(), serverCertificate); 194 } 195 196 private void generateTrustStore(X509Certificate serverCertificate) throws Exception { 197 String trustStorePath = getTruststoreFilePath(); 198 KeyStoreTestUtil.createTrustStore(trustStorePath, TRUST_STORE_PASSWORD, "serverTS", 199 serverCertificate); 200 } 201 202 private String getKeystoreFilePath() { 203 return String.format("%s/serverKS.%s", keyDir.getAbsolutePath(), "jks"); 204 } 205 206 private String getTruststoreFilePath() { 207 return String.format("%s/serverTS.%s", keyDir.getAbsolutePath(), "jks"); 208 } 209}