diff --git a/engines/base/pom.xml b/engines/base/pom.xml index c6f504b5..675b40ac 100644 --- a/engines/base/pom.xml +++ b/engines/base/pom.xml @@ -90,7 +90,6 @@ org.apache.httpcomponents httpclient - test org.apache.httpcomponents diff --git a/engines/base/src/main/java/org/alfresco/transform/base/config/MTLSConfig.java b/engines/base/src/main/java/org/alfresco/transform/base/config/MTLSConfig.java new file mode 100644 index 00000000..56777971 --- /dev/null +++ b/engines/base/src/main/java/org/alfresco/transform/base/config/MTLSConfig.java @@ -0,0 +1,172 @@ +/* + * #%L + * Alfresco Transform Core + * %% + * Copyright (C) 2005 - 2023 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.transform.base.config; + +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import static org.springframework.http.HttpHeaders.ACCEPT; +import static org.springframework.http.HttpHeaders.CONTENT_TYPE; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + +@Configuration +public class MTLSConfig { + + @Value("${filestore-url}") + private String url; + + @Value("${server.ssl.enabled}") + boolean sslEnabled; + + @Value("${server.ssl.key.store}") + private Resource keyStoreResource; + + //TODO: use some hashing algorithm + @Value("${server.ssl.key.password}") + private char[] keyPassword; + + //TODO: use some hashing algorithm + @Value("${server.ssl.key.store.password}") + private char[] keyStorePassword; + + @Value("${server.ssl.key.store.type}") + private String keyStoreType; + + @Value("${server.ssl.trust.store}") + private Resource trustStoreResource; + + //TODO: use some hashing algorithm + @Value("${server.ssl.trust.store.password}") + private char[] trustStorePassword; + + @Value("${server.ssl.trust.store.type}") + private String trustStoreType; + + @Bean + public WebClient client() throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException + { + if(sslEnabled) + { + HttpClient httpClient = getHttpClientWithMTLS(); + + return WebClient.builder().baseUrl(url.endsWith("/") ? url : url + "/") + .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE) + .defaultHeader(ACCEPT, APPLICATION_JSON_VALUE) + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + } else { + return WebClient.builder().baseUrl(url.endsWith("/") ? url : url + "/") + .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE) + .defaultHeader(ACCEPT, APPLICATION_JSON_VALUE) + .build(); + } + } + + private HttpClient getHttpClientWithMTLS() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { + KeyManagerFactory keyManagerFactory = initKeyManagerFactory(); + TrustManagerFactory trustManagerFactory = initTrustManagerFactory(); + + SslContext sslContext = SslContextBuilder.forClient() + .trustManager(trustManagerFactory) + .keyManager(keyManagerFactory) + .build(); + + HttpClient httpClient = HttpClient.create().secure(p -> p.sslContext(sslContext)); + return httpClient; + } + + private TrustManagerFactory initTrustManagerFactory() throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + KeyStore trustStore = getKeyStore(trustStoreType, trustStoreResource, trustStorePassword); + trustManagerFactory.init(trustStore); + return trustManagerFactory; + } + + private KeyManagerFactory initKeyManagerFactory() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException { + KeyStore clientKeyStore = getKeyStore(keyStoreType, keyStoreResource, keyStorePassword); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(keyStoreType); + keyManagerFactory.init(clientKeyStore, keyPassword); + return keyManagerFactory; + } + + private KeyStore getKeyStore(String keyStoreType, Resource keyStoreResource, char[] keyStorePassword) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { + KeyStore keyStore = KeyStore.getInstance(keyStoreType); + try (InputStream keyStoreInputStream = keyStoreResource.getInputStream()) { + keyStore.load(keyStoreInputStream, keyStorePassword); + } + return keyStore; + } + + @Bean + public RestTemplate restTemplate() throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, UnrecoverableKeyException { + if(sslEnabled) + { + return getRestTemplateWithMTLS(); + } else { + return new RestTemplate(); + } + } + + private RestTemplate getRestTemplateWithMTLS() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException, UnrecoverableKeyException { + KeyStore keyStore = getKeyStore(keyStoreType, keyStoreResource, keyStorePassword); + SSLContext sslContext = new SSLContextBuilder() + .loadKeyMaterial(keyStore, keyPassword) + .loadTrustMaterial(trustStoreResource.getURL(), trustStorePassword) + .build(); + + SSLConnectionSocketFactory sslContextFactory = new SSLConnectionSocketFactory(sslContext); + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslContextFactory).build(); + ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + return new RestTemplate(requestFactory); + } +} diff --git a/engines/base/src/main/java/org/alfresco/transform/base/config/WebApplicationConfig.java b/engines/base/src/main/java/org/alfresco/transform/base/config/WebApplicationConfig.java index 4ffb51e3..57f61390 100644 --- a/engines/base/src/main/java/org/alfresco/transform/base/config/WebApplicationConfig.java +++ b/engines/base/src/main/java/org/alfresco/transform/base/config/WebApplicationConfig.java @@ -35,7 +35,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; -import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -64,12 +63,6 @@ public class WebApplicationConfig implements WebMvcConfigurer .addPathPatterns(ENDPOINT_TRANSFORM, "/live", "/ready"); } - @Bean - public RestTemplate restTemplate() - { - return new RestTemplate(); - } - @Bean public TransformRequestValidator transformRequestValidator() { diff --git a/engines/base/src/main/java/org/alfresco/transform/base/sfs/SharedFileStoreClient.java b/engines/base/src/main/java/org/alfresco/transform/base/sfs/SharedFileStoreClient.java index 2772a35a..13e4b098 100644 --- a/engines/base/src/main/java/org/alfresco/transform/base/sfs/SharedFileStoreClient.java +++ b/engines/base/src/main/java/org/alfresco/transform/base/sfs/SharedFileStoreClient.java @@ -26,10 +26,7 @@ */ package org.alfresco.transform.base.sfs; -import static org.springframework.http.HttpHeaders.ACCEPT; -import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.HttpMethod.POST; -import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; import java.io.File; @@ -52,8 +49,6 @@ import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; import org.springframework.web.reactive.function.client.WebClient; -import javax.annotation.PostConstruct; - /** * Simple Rest client that call Alfresco Shared File Store */ @@ -68,17 +63,9 @@ public class SharedFileStoreClient @Autowired private RestTemplate restTemplate; + @Autowired private WebClient client; - @PostConstruct - public void init() - { - client = WebClient.builder().baseUrl(url.endsWith("/") ? url : url + "/") - .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE) - .defaultHeader(ACCEPT, APPLICATION_JSON_VALUE) - .build(); - } - /** * Retrieves a file from Shared File Store using given file reference *