Merge pull request #734 from Alfresco/feature/acs-4460_mtls_support

Feature/acs 4460 mtls support
This commit is contained in:
kcichonczyk 2023-03-15 12:31:37 +01:00 committed by GitHub
commit 5e592336c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 306 additions and 19 deletions

View File

@ -90,7 +90,6 @@
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
<scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>

View File

@ -0,0 +1,16 @@
/*
* Copyright 2015-2023 Alfresco Software, Ltd. All rights reserved.
*
* License rights for this program may be obtained from Alfresco Software, Ltd.
* pursuant to a written agreement and any use of this program without such an
* agreement is prohibited.
*/
package org.alfresco.transform.base;
import org.springframework.web.reactive.function.client.WebClient;
@FunctionalInterface
public interface WebClientBuilderAdjuster
{
void adjust(WebClient.Builder builder);
}

View File

@ -0,0 +1,196 @@
/*
* #%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 <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.transform.base.config;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.alfresco.transform.base.WebClientBuilderAdjuster;
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.SSLException;
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;
@Configuration
public class MTLSConfig {
@Value("${client.ssl.key-store:#{null}}")
private Resource keyStoreResource;
@Value("${client.ssl.key-store-password:}")
private char[] keyStorePassword;
@Value("${client.ssl.key-store-type:}")
private String keyStoreType;
@Value("${client.ssl.trust-store:#{null}}")
private Resource trustStoreResource;
@Value("${client.ssl.trust-store-password:}")
private char[] trustStorePassword;
@Value("${client.ssl.trust-store-type:}")
private String trustStoreType;
@Bean
public WebClientBuilderAdjuster webClientBuilderAdjuster(SslContextBuilder nettySslContextBuilder)
{
return builder -> {
if(isTlsOrMtlsConfigured())
{
HttpClient httpClientWithSslContext = null;
try {
httpClientWithSslContext = createHttpClientWithSslContext(nettySslContextBuilder);
} catch (SSLException e) {
throw new RuntimeException(e);
}
builder.clientConnector(new ReactorClientHttpConnector(httpClientWithSslContext));
}
};
}
@Bean
public RestTemplate restTemplate(SSLContextBuilder apacheSSLContextBuilder) throws IOException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, UnrecoverableKeyException
{
if(isTlsOrMtlsConfigured())
{
return createRestTemplateWithSslContext(apacheSSLContextBuilder);
} else {
return new RestTemplate();
}
}
@Bean
public SSLContextBuilder apacheSSLContextBuilder() throws CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, UnrecoverableKeyException {
SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
if(isKeystoreConfigured())
{
KeyStore keyStore = getKeyStore(keyStoreType, keyStoreResource, keyStorePassword);
sslContextBuilder.loadKeyMaterial(keyStore, keyStorePassword);
}
if(isTruststoreConfigured())
{
sslContextBuilder.loadTrustMaterial(trustStoreResource.getURL(), trustStorePassword);
}
return sslContextBuilder;
}
@Bean
public SslContextBuilder nettySslContextBuilder() throws UnrecoverableKeyException, CertificateException, KeyStoreException, IOException, NoSuchAlgorithmException {
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
if(isKeystoreConfigured())
{
KeyManagerFactory keyManagerFactory = initKeyManagerFactory();
sslContextBuilder.keyManager(keyManagerFactory);
}
if(isTruststoreConfigured())
{
TrustManagerFactory trustManagerFactory = initTrustManagerFactory();
sslContextBuilder.trustManager(trustManagerFactory);
}
return sslContextBuilder;
}
private boolean isTlsOrMtlsConfigured()
{
return isTruststoreConfigured() || isKeystoreConfigured();
}
private boolean isTruststoreConfigured()
{
return trustStoreResource != null;
}
private boolean isKeystoreConfigured()
{
return keyStoreResource != null;
}
private HttpClient createHttpClientWithSslContext(SslContextBuilder sslContextBuilder) throws SSLException {
SslContext sslContext = sslContextBuilder.build();
return HttpClient.create().secure(p -> p.sslContext(sslContext));
}
private RestTemplate createRestTemplateWithSslContext(SSLContextBuilder sslContextBuilder) throws NoSuchAlgorithmException, KeyManagementException {
SSLContext sslContext = sslContextBuilder.build();
SSLConnectionSocketFactory sslContextFactory = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslContextFactory).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(requestFactory);
}
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;
}
private TrustManagerFactory initTrustManagerFactory() throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException
{
KeyStore trustStore = getKeyStore(trustStoreType, trustStoreResource, trustStorePassword);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(trustStore);
return trustManagerFactory;
}
private KeyManagerFactory initKeyManagerFactory() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException
{
KeyStore clientKeyStore = getKeyStore(keyStoreType, keyStoreResource, keyStorePassword);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, keyStorePassword);
return keyManagerFactory;
}
}

View File

@ -35,7 +35,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType; 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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -64,12 +63,6 @@ public class WebApplicationConfig implements WebMvcConfigurer
.addPathPatterns(ENDPOINT_TRANSFORM, "/live", "/ready"); .addPathPatterns(ENDPOINT_TRANSFORM, "/live", "/ready");
} }
@Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
@Bean @Bean
public TransformRequestValidator transformRequestValidator() public TransformRequestValidator transformRequestValidator()
{ {

View File

@ -2,7 +2,7 @@
* #%L * #%L
* Alfresco Transform Core * Alfresco Transform Core
* %% * %%
* Copyright (C) 2005 - 2022 Alfresco Software Limited * Copyright (C) 2005 - 2023 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* - * -
@ -34,6 +34,7 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
import java.io.File; import java.io.File;
import org.alfresco.transform.base.WebClientBuilderAdjuster;
import org.alfresco.transform.exceptions.TransformException; import org.alfresco.transform.exceptions.TransformException;
import org.alfresco.transform.base.model.FileRefResponse; import org.alfresco.transform.base.model.FileRefResponse;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -53,6 +54,7 @@ import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.net.ssl.SSLException;
/** /**
* Simple Rest client that call Alfresco Shared File Store * Simple Rest client that call Alfresco Shared File Store
@ -68,12 +70,16 @@ public class SharedFileStoreClient
@Autowired @Autowired
private RestTemplate restTemplate; private RestTemplate restTemplate;
@Autowired
private WebClientBuilderAdjuster adjuster;
private WebClient client; private WebClient client;
@PostConstruct @PostConstruct
public void init() public void init() throws SSLException {
{ final WebClient.Builder clientBuilder = WebClient.builder();
client = WebClient.builder().baseUrl(url.endsWith("/") ? url : url + "/") adjuster.adjust(clientBuilder);
client = clientBuilder.baseUrl(url.endsWith("/") ? url : url + "/")
.defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE) .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
.defaultHeader(ACCEPT, APPLICATION_JSON_VALUE) .defaultHeader(ACCEPT, APPLICATION_JSON_VALUE)
.build(); .build();

View File

@ -0,0 +1,76 @@
package org.alfresco.transform.base;
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.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
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;
public class MtlsTestUtils {
private static final boolean MTLS_ENABLED = Boolean.parseBoolean(System.getProperty("test-mtls-enabled"));
public static boolean isMtlsEnabled()
{
return MTLS_ENABLED;
}
public static CloseableHttpClient httpClientWithMtls() throws NoSuchAlgorithmException, KeyManagementException, UnrecoverableKeyException, KeyStoreException, IOException, CertificateException
{
String keyStoreFile = System.getProperty("test-client-keystore-file");
String keyStoreType = System.getProperty("test-client-keystore-type");
char[] keyStorePassword = System.getProperty("test-client-keystore-password").toCharArray();
String trustStoreFile = System.getProperty("test-client-truststore-file");
String trustStoreType = System.getProperty("test-client-truststore-type");
char[] trustStorePassword = System.getProperty("test-client-truststore-password").toCharArray();
SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
try (InputStream keyStoreInputStream = new FileInputStream(keyStoreFile))
{
keyStore.load(keyStoreInputStream, keyStorePassword);
sslContextBuilder.loadKeyMaterial(keyStore, keyStorePassword);
}
File trustStore = new File(trustStoreFile);
sslContextBuilder.loadTrustMaterial(trustStore, trustStorePassword);
SSLContext sslContext = sslContextBuilder.build();
SSLConnectionSocketFactory sslContextFactory = new SSLConnectionSocketFactory(sslContext);
return HttpClients.custom().setSSLSocketFactory(sslContextFactory).build();
}
public static RestTemplate restTemplateWithMtls()
{
ClientHttpRequestFactory requestFactory = null;
try {
requestFactory = new HttpComponentsClientHttpRequestFactory(httpClientWithMtls());
} catch (Exception e) {
e.printStackTrace();
}
return new RestTemplate(requestFactory);
}
public static RestTemplate getRestTemplate()
{
return MtlsTestUtils.isMtlsEnabled() ? MtlsTestUtils.restTemplateWithMtls() : new RestTemplate();
}
public static CloseableHttpClient getHttpClient() throws UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
return MtlsTestUtils.isMtlsEnabled() ? MtlsTestUtils.httpClientWithMtls() : HttpClients.createDefault();
}
}

View File

@ -13,6 +13,7 @@ import static org.springframework.http.MediaType.MULTIPART_FORM_DATA;
import java.util.Map; import java.util.Map;
import org.alfresco.transform.base.MtlsTestUtils;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
@ -27,8 +28,8 @@ import org.springframework.web.client.RestTemplate;
*/ */
public class HttpClient public class HttpClient
{ {
private static final RestTemplate REST_TEMPLATE = new RestTemplate(); private static final RestTemplate REST_TEMPLATE = MtlsTestUtils.getRestTemplate();
public static ResponseEntity<Resource> sendTRequest( public static ResponseEntity<Resource> sendTRequest(
final String engineUrl, final String sourceFile, final String engineUrl, final String sourceFile,
final String sourceMimetype, final String targetMimetype, final String targetExtension) final String sourceMimetype, final String targetMimetype, final String targetExtension)

View File

@ -21,6 +21,7 @@ import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import org.alfresco.transform.base.MtlsTestUtils;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
@ -31,7 +32,6 @@ import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils; import org.apache.http.util.EntityUtils;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -56,7 +56,7 @@ public class SfsClient
((Logger) LoggerFactory.getLogger("org.apache.http.wire")).setAdditive(false); ((Logger) LoggerFactory.getLogger("org.apache.http.wire")).setAdditive(false);
} }
private static final String SFS_BASE_URL = "http://localhost:8099"; private static final String SFS_BASE_URL = MtlsTestUtils.isMtlsEnabled() ? "https://localhost:8099" : "http://localhost:8099";
public static String uploadFile(final String fileToUploadName) throws Exception public static String uploadFile(final String fileToUploadName) throws Exception
{ {
@ -75,7 +75,7 @@ public class SfsClient
.addPart("file", new FileBody(file, ContentType.DEFAULT_BINARY)) .addPart("file", new FileBody(file, ContentType.DEFAULT_BINARY))
.build()); .build());
try (CloseableHttpClient client = HttpClients.createDefault()) try (CloseableHttpClient client = MtlsTestUtils.getHttpClient())
{ {
final HttpResponse response = client.execute(post); final HttpResponse response = client.execute(post);
int status = response.getStatusLine().getStatusCode(); int status = response.getStatusLine().getStatusCode();
@ -134,7 +134,7 @@ public class SfsClient
sfsBaseUrl+"/alfresco/api/-default-/private/sfs/versions/1/file/{0}", sfsBaseUrl+"/alfresco/api/-default-/private/sfs/versions/1/file/{0}",
uuid)); uuid));
try (CloseableHttpClient client = HttpClients.createDefault()) try (CloseableHttpClient client = MtlsTestUtils.getHttpClient())
{ {
final HttpResponse response = client.execute(head); final HttpResponse response = client.execute(head);
final int status = response.getStatusLine().getStatusCode(); final int status = response.getStatusLine().getStatusCode();
@ -153,7 +153,7 @@ public class SfsClient
sfsBaseUrl+"/alfresco/api/-default-/private/sfs/versions/1/file/{0}", sfsBaseUrl+"/alfresco/api/-default-/private/sfs/versions/1/file/{0}",
uuid)); uuid));
try (CloseableHttpClient client = HttpClients.createDefault()) try (CloseableHttpClient client = MtlsTestUtils.getHttpClient())
{ {
final HttpResponse response = client.execute(get); final HttpResponse response = client.execute(get);
final int status = response.getStatusLine().getStatusCode(); final int status = response.getStatusLine().getStatusCode();