001/** 002 * Copyright (C) 2006-2025 Talend Inc. - www.talend.com 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.talend.sdk.components.vault.client; 017 018import static java.util.Optional.ofNullable; 019import static java.util.concurrent.TimeUnit.MILLISECONDS; 020 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.IOException; 024import java.security.KeyManagementException; 025import java.security.KeyStore; 026import java.security.KeyStoreException; 027import java.security.NoSuchAlgorithmException; 028import java.security.cert.CertificateException; 029import java.security.cert.X509Certificate; 030import java.util.List; 031import java.util.Objects; 032import java.util.Optional; 033import java.util.concurrent.ExecutorService; 034import java.util.concurrent.LinkedBlockingQueue; 035import java.util.concurrent.ThreadFactory; 036import java.util.concurrent.ThreadPoolExecutor; 037import java.util.concurrent.atomic.AtomicInteger; 038import java.util.stream.Stream; 039 040import javax.enterprise.context.ApplicationScoped; 041import javax.enterprise.inject.Disposes; 042import javax.enterprise.inject.Produces; 043import javax.inject.Inject; 044import javax.net.ssl.SSLContext; 045import javax.net.ssl.TrustManager; 046import javax.net.ssl.TrustManagerFactory; 047import javax.net.ssl.X509TrustManager; 048import javax.ws.rs.client.Client; 049import javax.ws.rs.client.ClientBuilder; 050import javax.ws.rs.client.WebTarget; 051 052import org.eclipse.microprofile.config.inject.ConfigProperty; 053import org.talend.sdk.components.vault.configuration.Documentation; 054 055import lombok.Data; 056import lombok.extern.slf4j.Slf4j; 057 058@Slf4j 059@Data 060@ApplicationScoped 061public class VaultClientSetup { 062 063 @Inject 064 @Documentation("HTTP connection timeout to vault server.") 065 @ConfigProperty(name = "talend.vault.cache.client.timeout.connect", defaultValue = "30000") 066 private Long connectTimeout; 067 068 @Inject 069 @Documentation("HTTP read timeout to vault server.") 070 @ConfigProperty(name = "talend.vault.cache.client.timeout.read", defaultValue = "30000") 071 private Long readTimeout; 072 073 @Inject 074 @Documentation("JAX-RS fully qualified name of the provides (message body readers/writers) for vault and component-server clients.") 075 @ConfigProperty(name = "talend.vault.cache.client.providers") 076 private Optional<String> providers; 077 078 @Inject 079 @Documentation("Should any certificate be accepted - only for dev purposes.") 080 @ConfigProperty(name = "talend.vault.cache.client.certificate.acceptAny", defaultValue = "false") 081 private Boolean acceptAnyCertificate; 082 083 @Inject 084 @Documentation("Where the keystore to use to connect to vault is located.") 085 @ConfigProperty(name = "talend.vault.cache.client.vault.certificate.keystore.location") 086 private Optional<String> vaultKeystoreLocation; 087 088 @Inject 089 @Documentation("The keystore type for `talend.vault.cache.client.vault.certificate.keystore.location`.") 090 @ConfigProperty(name = "talend.vault.cache.client.vault.certificate.keystore.type") 091 private Optional<String> vaultKeystoreType; 092 093 @Inject 094 @Documentation("The keystore password for `talend.vault.cache.client.vault.certificate.keystore.location`.") 095 @ConfigProperty(name = "talend.vault.cache.client.vault.certificate.keystore.password", defaultValue = "changeit") 096 private String vaultKeystorePassword; 097 098 @Inject 099 @Documentation("Valid hostnames for the Vault certificates (see `java.net.ssl.HostnameVerifier`).") 100 @ConfigProperty(name = "talend.vault.cache.client.vault.hostname.accepted", 101 defaultValue = "localhost,127.0.0.1,0:0:0:0:0:0:0:1") 102 private List<String> vaultHostnames; 103 104 @Inject 105 @Documentation("The truststore type for `talend.vault.cache.client.vault.certificate.keystore.location`.") 106 @ConfigProperty(name = "talend.vault.cache.client.vault.certificate.truststore.type") 107 private Optional<String> vaultTruststoreType; 108 109 @Inject 110 @Documentation("Thread pool max size for Vault client.") 111 @ConfigProperty(name = "talend.vault.cache.client.executor.vault.max", defaultValue = "256") 112 private Integer vaultExecutorMaxSize; 113 114 @Inject 115 @Documentation("Thread pool core size for Vault client.") 116 @ConfigProperty(name = "talend.vault.cache.client.executor.vault.core", defaultValue = "64") 117 private Integer vaultExecutorCoreSize; 118 119 @Inject 120 @Documentation("Thread keep alive (in ms) for Vault client thread pool.") 121 @ConfigProperty(name = "talend.vault.cache.client.executor.vault.keepAlive", defaultValue = "60000") 122 private Integer vaultExecutorKeepAlive; 123 124 @Inject 125 @Documentation("Base URL to connect to Vault.") 126 @ConfigProperty(name = "talend.vault.cache.vault.url", defaultValue = "no-vault") 127 private String vaultUrl; 128 129 @Produces 130 @ApplicationScoped 131 @VaultHttp 132 public WebTarget vaultTarget(@VaultHttp final Client client) { 133 return client.target(vaultUrl); 134 } 135 136 @Produces 137 @ApplicationScoped 138 @VaultHttp 139 public ExecutorService vaultExecutorService() { 140 return createExecutor(vaultExecutorCoreSize, vaultExecutorMaxSize, vaultExecutorKeepAlive, "vault"); 141 } 142 143 @VaultHttp 144 public void releaseVaultExecutor(@Disposes @VaultHttp final ExecutorService executorService) { 145 executorService.shutdownNow(); 146 } 147 148 @Produces 149 @ApplicationScoped 150 @VaultHttp 151 public Client vaultClient(@VaultHttp final ExecutorService executor) { 152 return createClient(executor, vaultKeystoreLocation, vaultKeystoreType, vaultKeystorePassword, 153 vaultTruststoreType, vaultHostnames).build(); 154 } 155 156 @VaultHttp 157 public void releaseVaultClient(@Disposes @VaultHttp final Client client) { 158 client.close(); 159 } 160 161 private ThreadPoolExecutor createExecutor(final int core, final int max, final long keepAlive, 162 final String nameMarker) { 163 return new ThreadPoolExecutor(core, max, keepAlive, MILLISECONDS, new LinkedBlockingQueue<>(), 164 new ThreadFactory() { 165 166 private final ThreadGroup group = ofNullable(System.getSecurityManager()) 167 .map(SecurityManager::getThreadGroup) 168 .orElseGet(() -> Thread.currentThread().getThreadGroup()); 169 170 private final AtomicInteger threadNumber = new AtomicInteger(1); 171 172 @Override 173 public Thread newThread(final Runnable r) { 174 final Thread t = new Thread(group, r, 175 "talend-vault-proxy-" + nameMarker + "-" + threadNumber.getAndIncrement(), 0); 176 if (t.isDaemon()) { 177 t.setDaemon(false); 178 } 179 if (t.getPriority() != Thread.NORM_PRIORITY) { 180 t.setPriority(Thread.NORM_PRIORITY); 181 } 182 return t; 183 } 184 }); 185 } 186 187 private ClientBuilder createClient(final ExecutorService executor, final Optional<String> keystoreLocation, 188 final Optional<String> keystoreType, final String keystorePassword, final Optional<String> truststoreType, 189 final List<String> serverHostnames) { 190 final ClientBuilder builder = ClientBuilder.newBuilder(); 191 builder.connectTimeout(connectTimeout, MILLISECONDS); 192 builder.readTimeout(readTimeout, MILLISECONDS); 193 builder.executorService(executor); 194 if (acceptAnyCertificate) { 195 builder.hostnameVerifier((host, session) -> true); 196 builder.sslContext(createUnsafeSSLContext()); 197 } else if (keystoreLocation.isPresent()) { 198 builder.hostnameVerifier((host, session) -> serverHostnames.contains(host)); 199 builder.sslContext(createSSLContext(keystoreLocation, keystoreType, keystorePassword, truststoreType)); 200 } 201 providers.map(it -> Stream.of(it.split(",")).map(String::trim).filter(v -> !v.isEmpty()).map(fqn -> { 202 try { 203 return Thread.currentThread().getContextClassLoader().loadClass(fqn).getConstructor().newInstance(); 204 } catch (final Exception e) { 205 log.warn("Can't add provider " + fqn + ": " + e.getMessage(), e); 206 return null; 207 } 208 }).filter(Objects::nonNull)).ifPresent(it -> it.forEach(builder::register)); 209 return builder; 210 } 211 212 private SSLContext createUnsafeSSLContext() { 213 final TrustManager[] trustManagers = { new X509TrustManager() { 214 215 @Override 216 public void checkClientTrusted(final X509Certificate[] x509Certificates, final String s) { 217 // no-op 218 } 219 220 @Override 221 public void checkServerTrusted(final X509Certificate[] x509Certificates, final String s) { 222 // no-op 223 } 224 225 @Override 226 public X509Certificate[] getAcceptedIssuers() { 227 return null; 228 } 229 } }; 230 try { 231 final SSLContext sslContext = SSLContext.getInstance("TLS"); 232 sslContext.init(null, trustManagers, new java.security.SecureRandom()); 233 return sslContext; 234 } catch (final NoSuchAlgorithmException | KeyManagementException e) { 235 throw new IllegalStateException(e); 236 } 237 } 238 239 private SSLContext createSSLContext(final Optional<String> keystoreLocation, final Optional<String> keystoreType, 240 final String keystorePassword, final Optional<String> truststoreType) { 241 final File source = new File(keystoreLocation.orElseThrow(IllegalArgumentException::new)); 242 if (!source.exists()) { 243 throw new IllegalArgumentException(source + " does not exist"); 244 } 245 final KeyStore keyStore; 246 try (final FileInputStream stream = new FileInputStream(source)) { 247 keyStore = KeyStore.getInstance(keystoreType.orElseGet(KeyStore::getDefaultType)); 248 keyStore.load(stream, keystorePassword.toCharArray()); 249 } catch (final KeyStoreException | NoSuchAlgorithmException e) { 250 throw new IllegalStateException(e); 251 } catch (final CertificateException | IOException e) { 252 throw new IllegalArgumentException(e); 253 } 254 try { 255 final TrustManagerFactory trustManagerFactory = 256 TrustManagerFactory.getInstance(truststoreType.orElseGet(TrustManagerFactory::getDefaultAlgorithm)); 257 trustManagerFactory.init(keyStore); 258 final SSLContext sslContext = SSLContext.getInstance("TLS"); 259 sslContext.init(null, trustManagerFactory.getTrustManagers(), new java.security.SecureRandom()); 260 return sslContext; 261 } catch (final KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) { 262 throw new IllegalStateException(e); 263 } 264 } 265}