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.rest; 019 020import static org.junit.Assert.assertEquals; 021 022import java.io.File; 023import java.lang.reflect.Method; 024import java.security.KeyPair; 025import java.security.cert.X509Certificate; 026import java.util.Optional; 027import org.apache.hadoop.conf.Configuration; 028import org.apache.hadoop.hbase.HBaseClassTestRule; 029import org.apache.hadoop.hbase.HBaseTestingUtility; 030import org.apache.hadoop.hbase.http.ssl.KeyStoreTestUtil; 031import org.apache.hadoop.hbase.rest.client.Client; 032import org.apache.hadoop.hbase.rest.client.Cluster; 033import org.apache.hadoop.hbase.rest.client.Response; 034import org.apache.hadoop.hbase.testclassification.MediumTests; 035import org.apache.hadoop.hbase.testclassification.RestTests; 036import org.junit.After; 037import org.junit.AfterClass; 038import org.junit.Before; 039import org.junit.BeforeClass; 040import org.junit.ClassRule; 041import org.junit.Test; 042import org.junit.experimental.categories.Category; 043import org.slf4j.Logger; 044import org.slf4j.LoggerFactory; 045 046@Category({ RestTests.class, MediumTests.class }) 047public class TestRESTServerSSL { 048 049 @ClassRule 050 public static final HBaseClassTestRule CLASS_RULE = 051 HBaseClassTestRule.forClass(TestRESTServerSSL.class); 052 053 private static final Logger LOG = LoggerFactory.getLogger(TestRESTServerSSL.class); 054 055 private static final String KEY_STORE_PASSWORD = "myKSPassword"; 056 private static final String TRUST_STORE_PASSWORD = "myTSPassword"; 057 058 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 059 private static final HBaseRESTTestingUtility REST_TEST_UTIL = new HBaseRESTTestingUtility(); 060 private static Client sslClient; 061 private static File keyDir; 062 private Configuration conf; 063 064 // Workaround for jdk8 292 bug. See https://github.com/bcgit/bc-java/issues/941 065 // Below is a workaround described in above URL. Issue fingered first in comments in 066 // HBASE-25920 Support Hadoop 3.3.1 067 private static void initializeAlgorithmId() { 068 try { 069 Class<?> algoId = Class.forName("sun.security.x509.AlgorithmId"); 070 Method method = algoId.getMethod("get", String.class); 071 method.setAccessible(true); 072 method.invoke(null, "PBEWithSHA1AndDESede"); 073 } catch (Exception e) { 074 LOG.warn("failed to initialize AlgorithmId", e); 075 } 076 } 077 078 @BeforeClass 079 public static void beforeClass() throws Exception { 080 initializeAlgorithmId(); 081 keyDir = initKeystoreDir(); 082 KeyPair keyPair = KeyStoreTestUtil.generateKeyPair("RSA"); 083 X509Certificate serverCertificate = 084 KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", keyPair, 30, "SHA1withRSA"); 085 086 generateTrustStore("jks", serverCertificate); 087 generateTrustStore("jceks", serverCertificate); 088 generateTrustStore("pkcs12", serverCertificate); 089 090 generateKeyStore("jks", keyPair, serverCertificate); 091 generateKeyStore("jceks", keyPair, serverCertificate); 092 generateKeyStore("pkcs12", keyPair, serverCertificate); 093 094 TEST_UTIL.startMiniCluster(); 095 } 096 097 @AfterClass 098 public static void afterClass() throws Exception { 099 // this will also delete the generated test keystore / teststore files, 100 // as we were placing them under the dataTestDir used by the minicluster 101 TEST_UTIL.shutdownMiniCluster(); 102 } 103 104 @Before 105 public void beforeEachTest() { 106 conf = new Configuration(TEST_UTIL.getConfiguration()); 107 conf.set(Constants.REST_SSL_ENABLED, "true"); 108 conf.set(Constants.REST_SSL_KEYSTORE_KEYPASSWORD, KEY_STORE_PASSWORD); 109 conf.set(Constants.REST_SSL_KEYSTORE_PASSWORD, KEY_STORE_PASSWORD); 110 conf.set(Constants.REST_SSL_TRUSTSTORE_PASSWORD, TRUST_STORE_PASSWORD); 111 } 112 113 @After 114 public void tearDownAfterTest() { 115 REST_TEST_UTIL.shutdownServletContainer(); 116 } 117 118 @Test 119 public void testSslConnection() throws Exception { 120 startRESTServerWithDefaultKeystoreType(); 121 122 Response response = sslClient.get("/version", Constants.MIMETYPE_TEXT); 123 assertEquals(200, response.getCode()); 124 125 // Default security headers 126 assertEquals("max-age=63072000;includeSubDomains;preload", 127 response.getHeader("Strict-Transport-Security")); 128 assertEquals("default-src https: data: 'unsafe-inline' 'unsafe-eval'", 129 response.getHeader("Content-Security-Policy")); 130 } 131 132 @Test(expected = org.apache.http.client.ClientProtocolException.class) 133 public void testNonSslClientDenied() throws Exception { 134 startRESTServerWithDefaultKeystoreType(); 135 136 Cluster localCluster = new Cluster().add("localhost", REST_TEST_UTIL.getServletPort()); 137 Client nonSslClient = new Client(localCluster, false); 138 139 nonSslClient.get("/version"); 140 } 141 142 @Test 143 public void testSslConnectionUsingKeystoreFormatJKS() throws Exception { 144 startRESTServer("jks"); 145 146 Response response = sslClient.get("/version", Constants.MIMETYPE_TEXT); 147 assertEquals(200, response.getCode()); 148 } 149 150 @Test 151 public void testSslConnectionUsingKeystoreFormatJCEKS() throws Exception { 152 startRESTServer("jceks"); 153 154 Response response = sslClient.get("/version", Constants.MIMETYPE_TEXT); 155 assertEquals(200, response.getCode()); 156 } 157 158 @Test 159 public void testSslConnectionUsingKeystoreFormatPKCS12() throws Exception { 160 startRESTServer("pkcs12"); 161 162 Response response = sslClient.get("/version", Constants.MIMETYPE_TEXT); 163 assertEquals(200, response.getCode()); 164 } 165 166 private static File initKeystoreDir() { 167 String dataTestDir = TEST_UTIL.getDataTestDir().toString(); 168 File keystoreDir = new File(dataTestDir, TestRESTServerSSL.class.getSimpleName() + "_keys"); 169 keystoreDir.mkdirs(); 170 return keystoreDir; 171 } 172 173 private static void generateKeyStore(String keyStoreType, KeyPair keyPair, 174 X509Certificate serverCertificate) throws Exception { 175 String keyStorePath = getKeystoreFilePath(keyStoreType); 176 KeyStoreTestUtil.createKeyStore(keyStorePath, KEY_STORE_PASSWORD, KEY_STORE_PASSWORD, 177 "serverKS", keyPair.getPrivate(), serverCertificate, keyStoreType); 178 } 179 180 private static void generateTrustStore(String trustStoreType, X509Certificate serverCertificate) 181 throws Exception { 182 String trustStorePath = getTruststoreFilePath(trustStoreType); 183 KeyStoreTestUtil.createTrustStore(trustStorePath, TRUST_STORE_PASSWORD, "serverTS", 184 serverCertificate, trustStoreType); 185 } 186 187 private static String getKeystoreFilePath(String keyStoreType) { 188 return String.format("%s/serverKS.%s", keyDir.getAbsolutePath(), keyStoreType); 189 } 190 191 private static String getTruststoreFilePath(String trustStoreType) { 192 return String.format("%s/serverTS.%s", keyDir.getAbsolutePath(), trustStoreType); 193 } 194 195 private void startRESTServerWithDefaultKeystoreType() throws Exception { 196 conf.set(Constants.REST_SSL_KEYSTORE_STORE, getKeystoreFilePath("jks")); 197 conf.set(Constants.REST_SSL_TRUSTSTORE_STORE, getTruststoreFilePath("jks")); 198 199 REST_TEST_UTIL.startServletContainer(conf); 200 Cluster localCluster = new Cluster().add("localhost", REST_TEST_UTIL.getServletPort()); 201 sslClient = new Client(localCluster, getTruststoreFilePath("jks"), 202 Optional.of(TRUST_STORE_PASSWORD), Optional.empty()); 203 } 204 205 private void startRESTServer(String storeType) throws Exception { 206 conf.set(Constants.REST_SSL_KEYSTORE_TYPE, storeType); 207 conf.set(Constants.REST_SSL_KEYSTORE_STORE, getKeystoreFilePath(storeType)); 208 209 conf.set(Constants.REST_SSL_TRUSTSTORE_STORE, getTruststoreFilePath(storeType)); 210 conf.set(Constants.REST_SSL_TRUSTSTORE_TYPE, storeType); 211 212 REST_TEST_UTIL.startServletContainer(conf); 213 Cluster localCluster = new Cluster().add("localhost", REST_TEST_UTIL.getServletPort()); 214 sslClient = new Client(localCluster, getTruststoreFilePath(storeType), 215 Optional.of(TRUST_STORE_PASSWORD), Optional.of(storeType)); 216 } 217 218}