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.io.crypto.tls; 019 020import java.io.IOException; 021import java.nio.file.Path; 022import java.nio.file.Paths; 023import java.security.GeneralSecurityException; 024import java.security.KeyStore; 025import java.security.Security; 026import java.security.cert.PKIXBuilderParameters; 027import java.security.cert.X509CertSelector; 028import java.time.Duration; 029import java.util.Arrays; 030import java.util.Objects; 031import java.util.concurrent.atomic.AtomicReference; 032import javax.net.ssl.CertPathTrustManagerParameters; 033import javax.net.ssl.KeyManager; 034import javax.net.ssl.KeyManagerFactory; 035import javax.net.ssl.TrustManager; 036import javax.net.ssl.TrustManagerFactory; 037import javax.net.ssl.X509ExtendedTrustManager; 038import javax.net.ssl.X509KeyManager; 039import javax.net.ssl.X509TrustManager; 040import org.apache.commons.lang3.StringUtils; 041import org.apache.hadoop.conf.Configuration; 042import org.apache.hadoop.hbase.exceptions.KeyManagerException; 043import org.apache.hadoop.hbase.exceptions.SSLContextException; 044import org.apache.hadoop.hbase.exceptions.TrustManagerException; 045import org.apache.hadoop.hbase.exceptions.X509Exception; 046import org.apache.hadoop.hbase.io.FileChangeWatcher; 047import org.apache.yetus.audience.InterfaceAudience; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051import org.apache.hbase.thirdparty.io.netty.handler.ssl.IdentityCipherSuiteFilter; 052import org.apache.hbase.thirdparty.io.netty.handler.ssl.OpenSsl; 053import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContext; 054import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslContextBuilder; 055import org.apache.hbase.thirdparty.io.netty.handler.ssl.SslProvider; 056 057/** 058 * Utility code for X509 handling Default cipher suites: Performance testing done by Facebook 059 * engineers shows that on Intel x86_64 machines, Java9 performs better with GCM and Java8 performs 060 * better with CBC, so these seem like reasonable defaults. 061 * <p/> 062 * This file has been copied from the Apache ZooKeeper project. 063 * @see <a href= 064 * "https://github.com/apache/zookeeper/blob/c74658d398cdc1d207aa296cb6e20de00faec03e/zookeeper-server/src/main/java/org/apache/zookeeper/common/X509Util.java">Base 065 * revision</a> 066 */ 067@InterfaceAudience.Private 068public final class X509Util { 069 070 private static final Logger LOG = LoggerFactory.getLogger(X509Util.class); 071 private static final char[] EMPTY_CHAR_ARRAY = new char[0]; 072 073 // 074 // Common tls configs across both server and client 075 // 076 static final String CONFIG_PREFIX = "hbase.rpc.tls."; 077 public static final String TLS_CONFIG_PROTOCOL = CONFIG_PREFIX + "protocol"; 078 public static final String TLS_CONFIG_KEYSTORE_LOCATION = CONFIG_PREFIX + "keystore.location"; 079 public static final String TLS_CONFIG_KEYSTORE_TYPE = CONFIG_PREFIX + "keystore.type"; 080 public static final String TLS_CONFIG_KEYSTORE_PASSWORD = CONFIG_PREFIX + "keystore.password"; 081 public static final String TLS_CONFIG_TRUSTSTORE_LOCATION = CONFIG_PREFIX + "truststore.location"; 082 public static final String TLS_CONFIG_TRUSTSTORE_TYPE = CONFIG_PREFIX + "truststore.type"; 083 public static final String TLS_CONFIG_TRUSTSTORE_PASSWORD = CONFIG_PREFIX + "truststore.password"; 084 public static final String TLS_CONFIG_CLR = CONFIG_PREFIX + "clr"; 085 public static final String TLS_CONFIG_OCSP = CONFIG_PREFIX + "ocsp"; 086 public static final String TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED = 087 CONFIG_PREFIX + "host-verification.reverse-dns.enabled"; 088 public static final String TLS_ENABLED_PROTOCOLS = CONFIG_PREFIX + "enabledProtocols"; 089 public static final String TLS_CIPHER_SUITES = CONFIG_PREFIX + "ciphersuites"; 090 public static final String TLS_CERT_RELOAD = CONFIG_PREFIX + "certReload"; 091 public static final String TLS_USE_OPENSSL = CONFIG_PREFIX + "useOpenSsl"; 092 093 // 094 // Role-scoped keystore/truststore configs for single-EKU certificate support. 095 // 096 097 /** 098 * When set, these take precedence over the unscoped keys above; when unset, the unscoped keys are 099 * used as a fallback so existing deployments keep working unchanged. 100 */ 101 static final String CLIENT_CONFIG_PREFIX = CONFIG_PREFIX + "client."; 102 static final String SERVER_CONFIG_PREFIX = CONFIG_PREFIX + "server."; 103 104 public static final String TLS_CONFIG_CLIENT_KEYSTORE_LOCATION = 105 CLIENT_CONFIG_PREFIX + "keystore.location"; 106 public static final String TLS_CONFIG_CLIENT_KEYSTORE_TYPE = 107 CLIENT_CONFIG_PREFIX + "keystore.type"; 108 public static final String TLS_CONFIG_CLIENT_KEYSTORE_PASSWORD = 109 CLIENT_CONFIG_PREFIX + "keystore.password"; 110 public static final String TLS_CONFIG_CLIENT_TRUSTSTORE_LOCATION = 111 CLIENT_CONFIG_PREFIX + "truststore.location"; 112 public static final String TLS_CONFIG_CLIENT_TRUSTSTORE_TYPE = 113 CLIENT_CONFIG_PREFIX + "truststore.type"; 114 public static final String TLS_CONFIG_CLIENT_TRUSTSTORE_PASSWORD = 115 CLIENT_CONFIG_PREFIX + "truststore.password"; 116 117 public static final String TLS_CONFIG_SERVER_KEYSTORE_LOCATION = 118 SERVER_CONFIG_PREFIX + "keystore.location"; 119 public static final String TLS_CONFIG_SERVER_KEYSTORE_TYPE = 120 SERVER_CONFIG_PREFIX + "keystore.type"; 121 public static final String TLS_CONFIG_SERVER_KEYSTORE_PASSWORD = 122 SERVER_CONFIG_PREFIX + "keystore.password"; 123 public static final String TLS_CONFIG_SERVER_TRUSTSTORE_LOCATION = 124 SERVER_CONFIG_PREFIX + "truststore.location"; 125 public static final String TLS_CONFIG_SERVER_TRUSTSTORE_TYPE = 126 SERVER_CONFIG_PREFIX + "truststore.type"; 127 public static final String TLS_CONFIG_SERVER_TRUSTSTORE_PASSWORD = 128 SERVER_CONFIG_PREFIX + "truststore.password"; 129 130 // 131 // Server-side specific configs 132 // 133 public static final String HBASE_SERVER_NETTY_TLS_ENABLED = "hbase.server.netty.tls.enabled"; 134 public static final String HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE = 135 "hbase.server.netty.tls.client.auth.mode"; 136 public static final String HBASE_SERVER_NETTY_TLS_VERIFY_CLIENT_HOSTNAME = 137 "hbase.server.netty.tls.verify.client.hostname"; 138 public static final String HBASE_SERVER_NETTY_TLS_SUPPORTPLAINTEXT = 139 "hbase.server.netty.tls.supportplaintext"; 140 141 /** 142 * Set the SSL wrapSize for netty. This is only a maximum wrap size. Buffers smaller than this 143 * will not be consolidated, but buffers larger than this will be split into multiple wrap 144 * buffers. The netty default of 16k is not great for hbase which tends to return larger payloads 145 * than that, meaning most responses end up getting chunked up. This leads to more memory 146 * contention in netty's PoolArena. See https://github.com/netty/netty/pull/13551 147 */ 148 public static final String HBASE_SERVER_NETTY_TLS_WRAP_SIZE = "hbase.server.netty.tls.wrapSize"; 149 public static final int DEFAULT_HBASE_SERVER_NETTY_TLS_WRAP_SIZE = 1024 * 1024; 150 // 151 // Client-side specific configs 152 // 153 public static final String HBASE_CLIENT_NETTY_TLS_ENABLED = "hbase.client.netty.tls.enabled"; 154 public static final String HBASE_CLIENT_NETTY_TLS_VERIFY_SERVER_HOSTNAME = 155 "hbase.client.netty.tls.verify.server.hostname"; 156 public static final String HBASE_CLIENT_NETTY_TLS_HANDSHAKETIMEOUT = 157 "hbase.client.netty.tls.handshaketimeout"; 158 public static final int DEFAULT_HANDSHAKE_DETECTION_TIMEOUT_MILLIS = 5000; 159 160 public static final String HBASE_TLS_FILEPOLL_INTERVAL_MILLIS = 161 CONFIG_PREFIX + "filepoll.interval.millis"; 162 // 1 minute 163 private static final long DEFAULT_FILE_POLL_INTERVAL = Duration.ofSeconds(60).toMillis(); 164 165 /** 166 * Enum specifying the client auth requirement of server-side TLS sockets created by this 167 * X509Util. 168 * <ul> 169 * <li>NONE - do not request a client certificate.</li> 170 * <li>WANT - request a client certificate, but allow anonymous clients to connect.</li> 171 * <li>NEED - require a client certificate, disconnect anonymous clients.</li> 172 * </ul> 173 * If the config property is not set, the default value is NEED. 174 */ 175 public enum ClientAuth { 176 NONE(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.NONE), 177 WANT(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.OPTIONAL), 178 NEED(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth.REQUIRE); 179 180 private final org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth nettyAuth; 181 182 ClientAuth(org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth nettyAuth) { 183 this.nettyAuth = nettyAuth; 184 } 185 186 /** 187 * Converts a property value to a ClientAuth enum. If the input string is empty or null, returns 188 * <code>ClientAuth.NEED</code>. 189 * @param prop the property string. 190 * @return the ClientAuth. 191 * @throws IllegalArgumentException if the property value is not "NONE", "WANT", "NEED", or 192 * empty/null. 193 */ 194 public static ClientAuth fromPropertyValue(String prop) { 195 if (prop == null || prop.length() == 0) { 196 return NEED; 197 } 198 return ClientAuth.valueOf(prop.toUpperCase()); 199 } 200 201 public org.apache.hbase.thirdparty.io.netty.handler.ssl.ClientAuth toNettyClientAuth() { 202 return nettyAuth; 203 } 204 } 205 206 // Jetty never leaves the peer-certificate trust anchors empty: with no truststore configured it 207 // falls back to the keystore, or to the JVM cacerts when no keystore is set either. WANT/NEED 208 // would then appear to work while trusting issuers the operator never configured, so fail fast. 209 public static void validateClientAuthTrustStore(ClientAuth clientAuth, String trustStoreLocation, 210 String clientAuthModeKey, String... trustStoreLocationKeys) { 211 if (clientAuth == ClientAuth.NONE || StringUtils.isNotBlank(trustStoreLocation)) { 212 return; 213 } 214 throw new IllegalArgumentException(clientAuthModeKey + "=" + clientAuth 215 + " requires a client-certificate trust root, but no truststore is configured." 216 + " Configure a truststore via [" + String.join(", ", trustStoreLocationKeys) 217 + "], or disable client certificate checking with " + clientAuthModeKey + "=NONE."); 218 } 219 220 private X509Util() { 221 // disabled 222 } 223 224 public static SslContext createSslContextForClient(Configuration config) 225 throws X509Exception, IOException { 226 227 SslContextBuilder sslContextBuilder = SslContextBuilder.forClient(); 228 229 configureOpenSslIfAvailable(sslContextBuilder, config); 230 TLSStore keyStore = 231 TLSStore.resolve(config, CLIENT_CONFIG_PREFIX, CONFIG_PREFIX, TLSStore.Keys.RPC_KEYSTORE); 232 String keyStoreLocation = keyStore.getLocation(); 233 char[] keyStorePassword = keyStore.getPasswordChars(); 234 String keyStoreType = keyStore.getType(); 235 236 if (keyStoreLocation.isEmpty()) { 237 LOG.warn("Neither {} nor {} specified", TLS_CONFIG_CLIENT_KEYSTORE_LOCATION, 238 TLS_CONFIG_KEYSTORE_LOCATION); 239 } else { 240 sslContextBuilder 241 .keyManager(createKeyManager(keyStoreLocation, keyStorePassword, keyStoreType)); 242 } 243 244 TLSStore trustStore = 245 TLSStore.resolve(config, CLIENT_CONFIG_PREFIX, CONFIG_PREFIX, TLSStore.Keys.RPC_TRUSTSTORE); 246 String trustStoreLocation = trustStore.getLocation(); 247 char[] trustStorePassword = trustStore.getPasswordChars(); 248 String trustStoreType = trustStore.getType(); 249 250 boolean sslCrlEnabled = config.getBoolean(TLS_CONFIG_CLR, false); 251 boolean sslOcspEnabled = config.getBoolean(TLS_CONFIG_OCSP, false); 252 253 boolean verifyServerHostname = 254 config.getBoolean(HBASE_CLIENT_NETTY_TLS_VERIFY_SERVER_HOSTNAME, true); 255 boolean allowReverseDnsLookup = config.getBoolean(TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED, true); 256 257 if (trustStoreLocation.isEmpty()) { 258 LOG.warn("Neither {} nor {} specified", TLS_CONFIG_CLIENT_TRUSTSTORE_LOCATION, 259 TLS_CONFIG_TRUSTSTORE_LOCATION); 260 } else { 261 sslContextBuilder 262 .trustManager(createTrustManager(trustStoreLocation, trustStorePassword, trustStoreType, 263 sslCrlEnabled, sslOcspEnabled, verifyServerHostname, allowReverseDnsLookup)); 264 } 265 266 sslContextBuilder.enableOcsp(sslOcspEnabled); 267 String[] enabledProtocols = getEnabledProtocols(config); 268 if (enabledProtocols != null) { 269 sslContextBuilder.protocols(enabledProtocols); 270 } 271 String[] cipherSuites = getCipherSuites(config); 272 if (cipherSuites == null) { 273 /* 274 * if cipher list is not explicitly defined, we use the most inclusive cipher list at the 275 * client side 276 */ 277 sslContextBuilder.ciphers(null, 278 IdentityCipherSuiteFilter.INSTANCE_DEFAULTING_TO_SUPPORTED_CIPHERS); 279 } else { 280 sslContextBuilder.ciphers(Arrays.asList(cipherSuites)); 281 } 282 283 return sslContextBuilder.build(); 284 } 285 286 /** 287 * Adds SslProvider.OPENSSL if OpenSsl is available and enabled. In order to make it available, 288 * one must ensure that a properly shaded netty-tcnative is on the classpath. Properly shaded 289 * means relocated to be prefixed with "org.apache.hbase.thirdparty" like the rest of the netty 290 * classes. We make available org.apache.hbase:hbase-openssl as a convenience module which one can 291 * use to pull in a shaded netty-tcnative statically linked against boringssl. 292 */ 293 private static boolean configureOpenSslIfAvailable(SslContextBuilder sslContextBuilder, 294 Configuration conf) { 295 boolean openSslEnabled = conf.getBoolean(TLS_USE_OPENSSL, true); 296 if (openSslEnabled && OpenSsl.isAvailable()) { 297 LOG.debug("Using netty-tcnative to accelerate SSL handling"); 298 sslContextBuilder.sslProvider(SslProvider.OPENSSL); 299 return true; 300 } else { 301 LOG.debug("Using default JDK SSL provider because netty-tcnative is not {}", 302 openSslEnabled ? "available" : "enabled"); 303 sslContextBuilder.sslProvider(SslProvider.JDK); 304 return false; 305 } 306 } 307 308 public static SslContext createSslContextForServer(Configuration config) 309 throws X509Exception, IOException { 310 TLSStore keyStore = 311 TLSStore.resolve(config, SERVER_CONFIG_PREFIX, CONFIG_PREFIX, TLSStore.Keys.RPC_KEYSTORE); 312 String keyStoreLocation = keyStore.getLocation(); 313 char[] keyStorePassword = keyStore.getPasswordChars(); 314 String keyStoreType = keyStore.getType(); 315 316 if (keyStoreLocation.isEmpty()) { 317 throw new SSLContextException("Keystore is required for SSL server: set either " 318 + TLS_CONFIG_SERVER_KEYSTORE_LOCATION + " or " + TLS_CONFIG_KEYSTORE_LOCATION); 319 } 320 321 SslContextBuilder sslContextBuilder; 322 sslContextBuilder = SslContextBuilder 323 .forServer(createKeyManager(keyStoreLocation, keyStorePassword, keyStoreType)); 324 325 configureOpenSslIfAvailable(sslContextBuilder, config); 326 TLSStore trustStore = 327 TLSStore.resolve(config, SERVER_CONFIG_PREFIX, CONFIG_PREFIX, TLSStore.Keys.RPC_TRUSTSTORE); 328 String trustStoreLocation = trustStore.getLocation(); 329 char[] trustStorePassword = trustStore.getPasswordChars(); 330 String trustStoreType = trustStore.getType(); 331 332 boolean sslCrlEnabled = config.getBoolean(TLS_CONFIG_CLR, false); 333 boolean sslOcspEnabled = config.getBoolean(TLS_CONFIG_OCSP, false); 334 335 ClientAuth clientAuth = 336 ClientAuth.fromPropertyValue(config.get(HBASE_SERVER_NETTY_TLS_CLIENT_AUTH_MODE)); 337 boolean verifyClientHostname = 338 config.getBoolean(HBASE_SERVER_NETTY_TLS_VERIFY_CLIENT_HOSTNAME, true); 339 boolean allowReverseDnsLookup = config.getBoolean(TLS_CONFIG_REVERSE_DNS_LOOKUP_ENABLED, true); 340 341 if (trustStoreLocation.isEmpty()) { 342 LOG.warn("Neither {} nor {} specified", TLS_CONFIG_SERVER_TRUSTSTORE_LOCATION, 343 TLS_CONFIG_TRUSTSTORE_LOCATION); 344 } else { 345 sslContextBuilder 346 .trustManager(createTrustManager(trustStoreLocation, trustStorePassword, trustStoreType, 347 sslCrlEnabled, sslOcspEnabled, verifyClientHostname, allowReverseDnsLookup)); 348 } 349 350 sslContextBuilder.enableOcsp(sslOcspEnabled); 351 String[] enabledProtocols = getEnabledProtocols(config); 352 if (enabledProtocols != null) { 353 sslContextBuilder.protocols(enabledProtocols); 354 } 355 String[] cipherSuites = getCipherSuites(config); 356 if (cipherSuites != null) { 357 sslContextBuilder.ciphers(Arrays.asList(cipherSuites)); 358 } 359 sslContextBuilder.clientAuth(clientAuth.toNettyClientAuth()); 360 361 return sslContextBuilder.build(); 362 } 363 364 /** 365 * Creates a key manager by loading the key store from the given file of the given type, 366 * optionally decrypting it using the given password. 367 * @param keyStoreLocation the location of the key store file. 368 * @param keyStorePassword optional password to decrypt the key store. If empty, assumes the key 369 * store is not encrypted. 370 * @param keyStoreType must be JKS, PEM, PKCS12, BCFKS or null. If null, attempts to 371 * autodetect the key store type from the file extension (e.g. .jks / 372 * .pem). 373 * @return the key manager. 374 * @throws KeyManagerException if something goes wrong. 375 */ 376 static X509KeyManager createKeyManager(String keyStoreLocation, char[] keyStorePassword, 377 String keyStoreType) throws KeyManagerException { 378 379 if (keyStorePassword == null) { 380 keyStorePassword = EMPTY_CHAR_ARRAY; 381 } 382 383 try { 384 KeyStoreFileType storeFileType = 385 KeyStoreFileType.fromPropertyValueOrFileName(keyStoreType, keyStoreLocation); 386 KeyStore ks = FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(storeFileType) 387 .setKeyStorePath(keyStoreLocation).setKeyStorePassword(keyStorePassword).build() 388 .loadKeyStore(); 389 390 KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); 391 kmf.init(ks, keyStorePassword); 392 393 for (KeyManager km : kmf.getKeyManagers()) { 394 if (km instanceof X509KeyManager) { 395 return (X509KeyManager) km; 396 } 397 } 398 throw new KeyManagerException("Couldn't find X509KeyManager"); 399 } catch (IOException | GeneralSecurityException | IllegalArgumentException e) { 400 throw new KeyManagerException(e); 401 } 402 } 403 404 /** 405 * Creates a trust manager by loading the trust store from the given file of the given type, 406 * optionally decrypting it using the given password. 407 * @param trustStoreLocation the location of the trust store file. 408 * @param trustStorePassword optional password to decrypt the trust store (only applies to JKS 409 * trust stores). If empty, assumes the trust store is not encrypted. 410 * @param trustStoreType must be JKS, PEM, PKCS12, BCFKS or null. If null, attempts to 411 * autodetect the trust store type from the file extension (e.g. .jks 412 * / .pem). 413 * @param crlEnabled enable CRL (certificate revocation list) checks. 414 * @param ocspEnabled enable OCSP (online certificate status protocol) checks. 415 * @param verifyHostName if true, ssl peer hostname must match name in certificate 416 * @param allowReverseDnsLookup if true, allow falling back to reverse dns lookup in verifying 417 * hostname 418 * @return the trust manager. 419 * @throws TrustManagerException if something goes wrong. 420 */ 421 static X509TrustManager createTrustManager(String trustStoreLocation, char[] trustStorePassword, 422 String trustStoreType, boolean crlEnabled, boolean ocspEnabled, boolean verifyHostName, 423 boolean allowReverseDnsLookup) throws TrustManagerException { 424 425 if (trustStorePassword == null) { 426 trustStorePassword = EMPTY_CHAR_ARRAY; 427 } 428 429 try { 430 KeyStoreFileType storeFileType = 431 KeyStoreFileType.fromPropertyValueOrFileName(trustStoreType, trustStoreLocation); 432 KeyStore ts = FileKeyStoreLoaderBuilderProvider.getBuilderForKeyStoreFileType(storeFileType) 433 .setTrustStorePath(trustStoreLocation).setTrustStorePassword(trustStorePassword).build() 434 .loadTrustStore(); 435 436 PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector()); 437 if (crlEnabled || ocspEnabled) { 438 pbParams.setRevocationEnabled(true); 439 System.setProperty("com.sun.net.ssl.checkRevocation", "true"); 440 if (crlEnabled) { 441 System.setProperty("com.sun.security.enableCRLDP", "true"); 442 } 443 if (ocspEnabled) { 444 Security.setProperty("ocsp.enable", "true"); 445 } 446 } else { 447 pbParams.setRevocationEnabled(false); 448 } 449 450 // Revocation checking is only supported with the PKIX algorithm 451 TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); 452 tmf.init(new CertPathTrustManagerParameters(pbParams)); 453 454 for (final TrustManager tm : tmf.getTrustManagers()) { 455 if (tm instanceof X509ExtendedTrustManager) { 456 return new HBaseTrustManager((X509ExtendedTrustManager) tm, verifyHostName, 457 allowReverseDnsLookup); 458 } 459 } 460 throw new TrustManagerException("Couldn't find X509TrustManager"); 461 } catch (IOException | GeneralSecurityException | IllegalArgumentException e) { 462 throw new TrustManagerException(e); 463 } 464 } 465 466 private static String[] getEnabledProtocols(Configuration config) { 467 String enabledProtocolsInput = config.get(TLS_ENABLED_PROTOCOLS); 468 if (enabledProtocolsInput == null) { 469 enabledProtocolsInput = config.get(TLS_CONFIG_PROTOCOL); 470 } 471 if (enabledProtocolsInput != null) { 472 return enabledProtocolsInput.split(","); 473 } else { 474 return null; 475 } 476 } 477 478 private static String[] getCipherSuites(Configuration config) { 479 String cipherSuitesInput = config.get(TLS_CIPHER_SUITES); 480 if (cipherSuitesInput == null) { 481 return null; 482 } else { 483 return cipherSuitesInput.split(","); 484 } 485 } 486 487 /** 488 * Enable certificate file reloading for the RPC <em>client</em> side by creating FileWatchers for 489 * the keystore and truststore whose paths are resolved under {@link #CLIENT_CONFIG_PREFIX} 490 * (role-scoped keys first, legacy keys as fallback). AtomicReferences will be set with the new 491 * instances. {@code resetContext} - if not null - will be called when the file has been modified. 492 * @param keystoreWatcher Reference to keystoreFileWatcher. 493 * @param trustStoreWatcher Reference to truststoreFileWatcher. 494 * @param resetContext Callback for file changes. 495 */ 496 public static void enableCertFileReloadingForClient(Configuration config, 497 AtomicReference<FileChangeWatcher> keystoreWatcher, 498 AtomicReference<FileChangeWatcher> trustStoreWatcher, Runnable resetContext) 499 throws IOException { 500 enableCertFileReloading(config, CLIENT_CONFIG_PREFIX, keystoreWatcher, trustStoreWatcher, 501 resetContext); 502 } 503 504 /** 505 * Enable certificate file reloading for the RPC <em>server</em> side. See 506 * {@link #enableCertFileReloadingForClient} for parameter semantics; the only difference is that 507 * paths are resolved under {@link #SERVER_CONFIG_PREFIX}, falling back to the legacy unscoped 508 * keys when unset. 509 */ 510 public static void enableCertFileReloadingForServer(Configuration config, 511 AtomicReference<FileChangeWatcher> keystoreWatcher, 512 AtomicReference<FileChangeWatcher> trustStoreWatcher, Runnable resetContext) 513 throws IOException { 514 enableCertFileReloading(config, SERVER_CONFIG_PREFIX, keystoreWatcher, trustStoreWatcher, 515 resetContext); 516 } 517 518 private static void enableCertFileReloading(Configuration config, String rolePrefix, 519 AtomicReference<FileChangeWatcher> keystoreWatcher, 520 AtomicReference<FileChangeWatcher> trustStoreWatcher, Runnable resetContext) 521 throws IOException { 522 // Resolve the watched paths through TLSStore so the file we watch is always the one the 523 // SslContext loaded: the prefix (role-scoped or legacy) is chosen once, from the location. 524 String keyStoreLocation = 525 TLSStore.resolve(config, rolePrefix, CONFIG_PREFIX, TLSStore.Keys.RPC_KEYSTORE).getLocation(); 526 String trustStoreLocation = TLSStore 527 .resolve(config, rolePrefix, CONFIG_PREFIX, TLSStore.Keys.RPC_TRUSTSTORE).getLocation(); 528 keystoreWatcher.set(newFileChangeWatcher(config, keyStoreLocation, resetContext)); 529 // we are using the same callback for both. there's no reason to kick off two 530 // threads if keystore/truststore are both at the same location 531 if (!keyStoreLocation.equals(trustStoreLocation)) { 532 trustStoreWatcher.set(newFileChangeWatcher(config, trustStoreLocation, resetContext)); 533 } 534 } 535 536 private static FileChangeWatcher newFileChangeWatcher(Configuration config, String fileLocation, 537 Runnable resetContext) throws IOException { 538 if (fileLocation == null || fileLocation.isEmpty() || resetContext == null) { 539 return null; 540 } 541 final Path filePath = Paths.get(fileLocation).toAbsolutePath(); 542 FileChangeWatcher fileChangeWatcher = 543 new FileChangeWatcher(filePath, Objects.toString(filePath.getFileName()), 544 Duration 545 .ofMillis(config.getLong(HBASE_TLS_FILEPOLL_INTERVAL_MILLIS, DEFAULT_FILE_POLL_INTERVAL)), 546 watchEventFilePath -> handleWatchEvent(watchEventFilePath, resetContext)); 547 fileChangeWatcher.start(); 548 return fileChangeWatcher; 549 } 550 551 /** 552 * Handler for watch events that let us know a file we may care about has changed on disk. 553 */ 554 private static void handleWatchEvent(Path filePath, Runnable resetContext) { 555 LOG.info("Attempting to reset default SSL context after receiving watch event on file {}", 556 filePath); 557 resetContext.run(); 558 } 559}