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