diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 72c6e85067..c16b9a8ac1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -313,6 +313,10 @@ jobs:
- testSuite: SearchTestSuite
compose-profile: default
mvn-options: '-Dindex.subsystem.name=solr6'
+ - testSuite: MTLSTestSuite
+ compose-profile: with-mtls-transform-core-aio
+ mtls: true
+ mvn-options: '-Dencryption.ssl.keystore.location=${GITHUB_WORKSPACE}/keystores/alfresco/alfresco.keystore -Dencryption.ssl.truststore.location=${GITHUB_WORKSPACE}/keystores/alfresco/alfresco.truststore'
steps:
- uses: actions/checkout@v3
- uses: Alfresco/alfresco-build-tools/.github/actions/get-build-info@v1.33.0
@@ -321,6 +325,11 @@ jobs:
run: bash ./scripts/ci/init.sh
- name: "Set transformers tag"
run: echo "TRANSFORMERS_TAG=$(mvn help:evaluate -Dexpression=dependency.alfresco-transform-core.version -q -DforceStdout)" >> $GITHUB_ENV
+ - name: "Generate Keystores and Truststores for Mutual TLS configuration"
+ if: ${{ matrix.mtls }}
+ run: |
+ git clone -b "master" --depth=1 "https://${{ secrets.BOT_GITHUB_USERNAME }}:${{ secrets.BOT_GITHUB_TOKEN }}@github.com/Alfresco/alfresco-ssl-generator.git"
+ bash ./scripts/ci/generate_keystores.sh
- name: "Set up the environment"
run: |
if [ -e ./scripts/ci/tests/${{ matrix.testSuite }}-setup.sh ]; then
diff --git a/core/pom.xml b/core/pom.xml
index 8c9e8218bf..5459094493 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -137,6 +137,10 @@
commons-dbcp2
test
+
+ org.apache.httpcomponents
+ httpclient
+
diff --git a/core/src/main/java/org/alfresco/httpclient/HttpClient4Factory.java b/core/src/main/java/org/alfresco/httpclient/HttpClient4Factory.java
new file mode 100644
index 0000000000..115ffdeee0
--- /dev/null
+++ b/core/src/main/java/org/alfresco/httpclient/HttpClient4Factory.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2023 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+
+package org.alfresco.httpclient;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequestInterceptor;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.client.StandardHttpRequestRetryHandler;
+
+
+public class HttpClient4Factory
+{
+ protected static final String TLS_PROTOCOL = "TLS";
+ protected static final String HTTPS_PROTOCOL = "https";
+ protected static final String HTTP_TARGET_HOST = "http.target_host";
+ protected static final String TLS_V_1_2 = "TLSv1.2";
+ protected static final String TLS_V_1_3 = "TLSv1.3";
+
+ private static SSLContext createSSLContext(HttpClientConfig config)
+ {
+ KeyManager[] keyManagers = config.getKeyStore().createKeyManagers();
+ TrustManager[] trustManagers = config.getTrustStore().createTrustManagers();
+
+ try
+ {
+ SSLContext sslcontext = SSLContext.getInstance(TLS_PROTOCOL);
+ sslcontext.init(keyManagers, trustManagers, null);
+ return sslcontext;
+ }
+ catch(Throwable e)
+ {
+ throw new AlfrescoRuntimeException("Unable to create SSL context", e);
+ }
+ }
+
+ public static CloseableHttpClient createHttpClient(HttpClientConfig config)
+ {
+ return createHttpClient(config, null);
+ }
+
+ public static CloseableHttpClient createHttpClient(HttpClientConfig config, HttpClientConnectionManager connectionManager)
+ {
+ HttpClientBuilder clientBuilder = HttpClients.custom();
+
+ if(config.isMTLSEnabled())
+ {
+ clientBuilder.addInterceptorFirst((HttpRequestInterceptor) (request, context) -> {
+ if (!((HttpHost) context.getAttribute(HTTP_TARGET_HOST)).getSchemeName().equals(HTTPS_PROTOCOL))
+ {
+ String msg = "mTLS is enabled but provided URL does not use a secured protocol";
+ throw new HttpClientException(msg);
+ }
+ });
+
+ SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
+ createSSLContext(config),
+ new String[] { TLS_V_1_2, TLS_V_1_3 },
+ null,
+ config.isHostnameVerificationDisabled() ? new NoopHostnameVerifier() : SSLConnectionSocketFactory.getDefaultHostnameVerifier());
+ clientBuilder.setSSLSocketFactory(sslConnectionSocketFactory);
+ }
+
+ if(connectionManager != null)
+ {
+ clientBuilder.setConnectionManager(connectionManager);
+ }
+
+ RequestConfig requestConfig = RequestConfig.custom()
+ .setConnectTimeout(config.getConnectionTimeout())
+ .setSocketTimeout(config.getSocketTimeout())
+ .setConnectionRequestTimeout(config.getConnectionRequestTimeout())
+ .build();
+
+ clientBuilder.setDefaultRequestConfig(requestConfig);
+ clientBuilder.setMaxConnTotal(config.getMaxTotalConnections());
+ clientBuilder.setMaxConnPerRoute(config.getMaxHostConnections());
+ clientBuilder.setRetryHandler(new StandardHttpRequestRetryHandler(5, false));
+
+ return clientBuilder.build();
+ }
+
+}
diff --git a/core/src/main/java/org/alfresco/httpclient/HttpClientConfig.java b/core/src/main/java/org/alfresco/httpclient/HttpClientConfig.java
new file mode 100644
index 0000000000..7700c3382c
--- /dev/null
+++ b/core/src/main/java/org/alfresco/httpclient/HttpClientConfig.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+
+package org.alfresco.httpclient;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import java.util.stream.Collectors;
+
+import org.alfresco.encryption.AlfrescoKeyStore;
+import org.alfresco.encryption.AlfrescoKeyStoreImpl;
+import org.alfresco.encryption.KeyResourceLoader;
+import org.alfresco.encryption.ssl.SSLEncryptionParameters;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class HttpClientConfig
+{
+ private static final String HTTPCLIENT_CONFIG = "httpclient.config.";
+
+ protected static final Log LOGGER = LogFactory.getLog(HttpClientConfig.class);
+
+ private Properties properties;
+ private String serviceName;
+
+ private SSLEncryptionParameters sslEncryptionParameters;
+ private KeyResourceLoader keyResourceLoader;
+
+ private AlfrescoKeyStore keyStore;
+ private AlfrescoKeyStore trustStore;
+
+ private Map config;
+
+ public void setProperties(Properties properties)
+ {
+ this.properties = properties;
+ }
+
+ public void setServiceName(String serviceName)
+ {
+ this.serviceName = serviceName;
+ }
+
+ public void setSslEncryptionParameters(SSLEncryptionParameters sslEncryptionParameters)
+ {
+ this.sslEncryptionParameters = sslEncryptionParameters;
+ }
+
+ public void setKeyResourceLoader(KeyResourceLoader keyResourceLoader)
+ {
+ this.keyResourceLoader = keyResourceLoader;
+ }
+
+ public AlfrescoKeyStore getKeyStore()
+ {
+ return keyStore;
+ }
+
+ public AlfrescoKeyStore getTrustStore()
+ {
+ return trustStore;
+ }
+
+ public void init()
+ {
+ this.keyStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getKeyStoreParameters(), keyResourceLoader);
+ this.trustStore = new AlfrescoKeyStoreImpl(sslEncryptionParameters.getTrustStoreParameters(), keyResourceLoader);
+
+ config = retrieveConfig(serviceName);
+ checkUnsupportedProperties(config);
+ }
+
+ /**
+ * Method used for retrieving HttpClient config from Global Properties
+ * @param serviceName name of used service
+ * @return map of properties
+ */
+ private Map retrieveConfig(String serviceName)
+ {
+ return properties.keySet().stream()
+ .filter(key -> key instanceof String)
+ .map(Object::toString)
+ .filter(key -> key.startsWith(HTTPCLIENT_CONFIG + serviceName))
+ .collect(Collectors.toMap(
+ key -> key.replace(HTTPCLIENT_CONFIG + serviceName + ".", ""),
+ key -> properties.getProperty(key, null)));
+ }
+
+ private void checkUnsupportedProperties(Map config)
+ {
+ config.keySet().stream()
+ .filter(propertyName -> !HttpClientPropertiesEnum.isPropertyNameSupported(propertyName))
+ .forEach(propertyName -> LOGGER.warn(String.format("For service [%s], an unsupported property [%s] is set", serviceName, propertyName)));
+ }
+
+ private Integer getIntegerProperty(HttpClientPropertiesEnum property)
+ {
+ return Integer.parseInt(extractValueFromConfig(property).orElse("0"));
+ }
+
+ private Boolean getBooleanProperty(HttpClientPropertiesEnum property)
+ {
+ return Boolean.parseBoolean(extractValueFromConfig(property).orElse("false"));
+ }
+
+ private Optional extractValueFromConfig(HttpClientPropertiesEnum property)
+ {
+ Optional optionalProperty = Optional.ofNullable(config.get(property.name));
+ if(property.isRequired && optionalProperty.isEmpty())
+ {
+ String msg = String.format("Required property: '%s' is empty.", property.name);
+ throw new HttpClientException(msg);
+ }
+ return optionalProperty;
+ }
+
+ public Integer getConnectionTimeout()
+ {
+ return getIntegerProperty(HttpClientPropertiesEnum.CONNECTION_REQUEST_TIMEOUT);
+ }
+
+ public Integer getSocketTimeout()
+ {
+ return getIntegerProperty(HttpClientPropertiesEnum.SOCKET_TIMEOUT);
+ }
+
+ public Integer getConnectionRequestTimeout()
+ {
+ return getIntegerProperty(HttpClientPropertiesEnum.CONNECTION_REQUEST_TIMEOUT);
+ }
+
+ public Integer getMaxTotalConnections()
+ {
+ return getIntegerProperty(HttpClientPropertiesEnum.MAX_TOTAL_CONNECTIONS);
+ }
+
+ public Integer getMaxHostConnections()
+ {
+ return getIntegerProperty(HttpClientPropertiesEnum.MAX_HOST_CONNECTIONS);
+ }
+
+ public Boolean isMTLSEnabled()
+ {
+ return getBooleanProperty(HttpClientPropertiesEnum.MTLS_ENABLED);
+ }
+
+ public boolean isHostnameVerificationDisabled()
+ {
+ return getBooleanProperty(HttpClientPropertiesEnum.HOSTNAME_VERIFICATION_DISABLED);
+ }
+
+ private enum HttpClientPropertiesEnum
+ {
+ CONNECTION_TIMEOUT("connectionTimeout", true),
+ SOCKET_TIMEOUT("socketTimeout", true),
+ CONNECTION_REQUEST_TIMEOUT("connectionRequestTimeout", true),
+ MAX_TOTAL_CONNECTIONS("maxTotalConnections", true),
+ MAX_HOST_CONNECTIONS("maxHostConnections", true),
+ HOSTNAME_VERIFICATION_DISABLED("hostnameVerificationDisabled", false),
+ MTLS_ENABLED("mTLSEnabled", true);
+
+ private final String name;
+ private final Boolean isRequired;
+
+ HttpClientPropertiesEnum(String propertyName, Boolean isRequired)
+ {
+ this.name = propertyName;
+ this.isRequired = isRequired;
+ }
+
+ private static final List supportedProperties = new ArrayList<>();
+
+ static {
+ for (HttpClientPropertiesEnum property : HttpClientPropertiesEnum.values()) {
+ supportedProperties.add(property.name);
+ }
+ }
+
+ public static boolean isPropertyNameSupported(String propertyName) {
+ return supportedProperties.contains(propertyName);
+ }
+ }
+}
diff --git a/core/src/main/java/org/alfresco/httpclient/HttpClientException.java b/core/src/main/java/org/alfresco/httpclient/HttpClientException.java
new file mode 100644
index 0000000000..9dc0f38784
--- /dev/null
+++ b/core/src/main/java/org/alfresco/httpclient/HttpClientException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+
+package org.alfresco.httpclient;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+
+public class HttpClientException extends AlfrescoRuntimeException
+{
+ public HttpClientException(String msgId)
+ {
+ super(msgId);
+ }
+}
diff --git a/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformImpl.java b/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformImpl.java
index 942afac149..afcc0a16f7 100644
--- a/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformImpl.java
+++ b/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformImpl.java
@@ -25,6 +25,7 @@
*/
package org.alfresco.repo.content.transform;
+import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.repo.content.metadata.AsynchronousExtractor;
import org.alfresco.repo.rendition2.RenditionDefinition2;
import org.alfresco.service.cmr.repository.ContentReader;
@@ -60,11 +61,12 @@ public class LocalTransformImpl extends AbstractLocalTransform
boolean retryTransformOnDifferentMimeType,
Set transformsTransformOptions,
LocalTransformServiceRegistry localTransformServiceRegistry, String baseUrl,
+ HttpClientConfig httpClientConfig,
int startupRetryPeriodSeconds)
{
super(name, transformerDebug, mimetypeService, strictMimeTypeCheck, strictMimetypeExceptions,
retryTransformOnDifferentMimeType, transformsTransformOptions, localTransformServiceRegistry);
- remoteTransformerClient = new RemoteTransformerClient(name, baseUrl);
+ remoteTransformerClient = new RemoteTransformerClient(name, baseUrl, httpClientConfig);
remoteTransformerClient.setStartupRetryPeriodSeconds(startupRetryPeriodSeconds);
checkAvailability();
diff --git a/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformServiceRegistry.java b/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformServiceRegistry.java
index 74447ad753..34ed4058c0 100644
--- a/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformServiceRegistry.java
+++ b/repository/src/main/java/org/alfresco/repo/content/transform/LocalTransformServiceRegistry.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
- * Copyright (C) 2019 - 2022 Alfresco Software Limited
+ * Copyright (C) 2019 - 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
@@ -35,6 +35,8 @@ import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import org.alfresco.httpclient.HttpClient4Factory;
+import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.transform.config.CoreFunction;
import org.alfresco.transform.config.TransformOptionGroup;
@@ -77,6 +79,17 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
private boolean strictMimeTypeCheck;
private Map> strictMimetypeExceptions;
private boolean retryTransformOnDifferentMimeType;
+ private HttpClientConfig httpClientConfig;
+
+ public HttpClientConfig getHttpClientConfig()
+ {
+ return httpClientConfig;
+ }
+
+ public void setHttpClientConfig(HttpClientConfig httpClientConfig)
+ {
+ this.httpClientConfig = httpClientConfig;
+ }
public void setPipelineConfigDir(String pipelineConfigDir)
{
@@ -134,7 +147,7 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
@Override
public boolean readConfig() throws IOException
{
- CombinedConfig combinedConfig = new CombinedConfig(getLog(), this);
+ CombinedConfig combinedConfig = new CombinedConfig(getLog(), this, httpClientConfig);
List urls = getTEngineUrlsSortedByName();
boolean successReadingConfig = combinedConfig.addRemoteConfig(urls, "T-Engine");
successReadingConfig &= combinedConfig.addLocalConfig("alfresco/transforms");
@@ -188,7 +201,8 @@ public class LocalTransformServiceRegistry extends TransformServiceRegistryImpl
int startupRetryPeriodSeconds = getStartupRetryPeriodSeconds(name);
localTransform = new LocalTransformImpl(name, transformerDebug, mimetypeService,
strictMimeTypeCheck, strictMimetypeExceptions, retryTransformOnDifferentMimeType,
- transformsTransformOptions, this, baseUrl, startupRetryPeriodSeconds);
+ transformsTransformOptions, this, baseUrl, httpClientConfig,
+ startupRetryPeriodSeconds);
}
else if (isPipeline)
{
diff --git a/repository/src/main/java/org/alfresco/repo/content/transform/RemoteTransformerClient.java b/repository/src/main/java/org/alfresco/repo/content/transform/RemoteTransformerClient.java
index a3713f7cb9..c03f9270b4 100644
--- a/repository/src/main/java/org/alfresco/repo/content/transform/RemoteTransformerClient.java
+++ b/repository/src/main/java/org/alfresco/repo/content/transform/RemoteTransformerClient.java
@@ -30,6 +30,8 @@ import java.io.InputStream;
import java.util.StringJoiner;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.httpclient.HttpClient4Factory;
+import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.Pair;
@@ -57,6 +59,8 @@ import org.apache.http.util.EntityUtils;
*/
public class RemoteTransformerClient
{
+ private final HttpClientConfig httpClientConfig;
+
private final String name;
private final String baseUrl;
@@ -70,10 +74,11 @@ public class RemoteTransformerClient
// Only changed once on success. This is stored so it can always be returned.
private Pair checkResult = new Pair<>(null, null);
- public RemoteTransformerClient(String name, String baseUrl)
+ public RemoteTransformerClient(String name, String baseUrl, HttpClientConfig httpClientConfig)
{
this.name = name;
this.baseUrl = baseUrl == null || baseUrl.trim().isEmpty() ? null : baseUrl.trim();
+ this.httpClientConfig = httpClientConfig;
}
public void setStartupRetryPeriodSeconds(int startupRetryPeriodSeconds)
@@ -129,7 +134,7 @@ public class RemoteTransformerClient
try
{
- try (CloseableHttpClient httpclient = HttpClients.createDefault())
+ try (CloseableHttpClient httpclient = HttpClient4Factory.createHttpClient(httpClientConfig))
{
try (CloseableHttpResponse response = execute(httpclient, httppost))
{
@@ -232,7 +237,7 @@ public class RemoteTransformerClient
try
{
- try (CloseableHttpClient httpclient = HttpClients.createDefault())
+ try (CloseableHttpClient httpclient = HttpClient4Factory.createHttpClient(httpClientConfig))
{
try (CloseableHttpResponse response = execute(httpclient, httpGet))
{
diff --git a/repository/src/main/java/org/alfresco/transform/registry/CombinedConfig.java b/repository/src/main/java/org/alfresco/transform/registry/CombinedConfig.java
index dfcd8fb466..414d15312f 100644
--- a/repository/src/main/java/org/alfresco/transform/registry/CombinedConfig.java
+++ b/repository/src/main/java/org/alfresco/transform/registry/CombinedConfig.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
- * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * 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
@@ -28,6 +28,8 @@ package org.alfresco.transform.registry;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.httpclient.HttpClient4Factory;
+import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.repo.content.transform.LocalPassThroughTransform;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.transform.config.TransformConfig;
@@ -39,7 +41,6 @@ import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
@@ -67,8 +68,11 @@ public class CombinedConfig extends CombinedTransformConfig
private ConfigFileFinder configFileFinder;
private int tEngineCount;
- public CombinedConfig(Log log, AbstractTransformRegistry registry)
+ private final HttpClientConfig httpClientConfig;
+
+ public CombinedConfig(Log log, AbstractTransformRegistry registry, HttpClientConfig httpClientConfig)
{
+ this.httpClientConfig = httpClientConfig;
this.log = log;
configFileFinder = new ConfigFileFinder(jsonObjectMapper)
@@ -87,88 +91,84 @@ public class CombinedConfig extends CombinedTransformConfig
return configFileFinder.readFiles(path, log);
}
- public boolean addRemoteConfig(List urls, String remoteType)
+ public boolean addRemoteConfig(List urls, String remoteType) throws IOException
{
- boolean successReadingConfig = true;
- for (String url : urls)
+ try(CloseableHttpClient httpclient = HttpClient4Factory.createHttpClient(httpClientConfig))
{
- if (addRemoteConfig(url, remoteType))
+ boolean successReadingConfig = true;
+ for (String url : urls)
{
- tEngineCount++ ;
- }
- else
- {
- successReadingConfig = false;
+ if (addRemoteConfig(httpclient, url, remoteType))
+ {
+ tEngineCount++;
+ } else
+ {
+ successReadingConfig = false;
+ }
}
+ return successReadingConfig;
}
- return successReadingConfig;
}
- private boolean addRemoteConfig(String baseUrl, String remoteType)
+ private boolean addRemoteConfig(CloseableHttpClient httpclient, String baseUrl, String remoteType)
{
String url = baseUrl + (baseUrl.endsWith("/") ? "" : "/") + ENDPOINT_TRANSFORM_CONFIG_LATEST;
HttpGet httpGet = new HttpGet(url);
boolean successReadingConfig = true;
try
{
- try (CloseableHttpClient httpclient = HttpClients.createDefault())
+ try (CloseableHttpResponse response = execute(httpclient, httpGet))
{
- try (CloseableHttpResponse response = execute(httpclient, httpGet))
+ StatusLine statusLine = response.getStatusLine();
+ if (statusLine == null)
{
- StatusLine statusLine = response.getStatusLine();
- if (statusLine == null)
+ throw new AlfrescoRuntimeException(remoteType+" on " + url+" returned no status ");
+ }
+ HttpEntity resEntity = response.getEntity();
+ if (resEntity != null)
+ {
+ int statusCode = statusLine.getStatusCode();
+ if (statusCode == 200)
{
- throw new AlfrescoRuntimeException(remoteType+" on " + url+" returned no status ");
- }
- HttpEntity resEntity = response.getEntity();
- if (resEntity != null)
- {
- int statusCode = statusLine.getStatusCode();
- if (statusCode == 200)
+ try
{
- try
+ String content = getContent(resEntity);
+ try (StringReader reader = new StringReader(content))
{
- String content = getContent(resEntity);
- try (StringReader reader = new StringReader(content))
+ int transformCount = transformerCount();
+ configFileFinder.readFile(reader, remoteType+" on "+baseUrl, "json", baseUrl, log);
+ if (transformCount == transformerCount())
{
- int transformCount = transformerCount();
- configFileFinder.readFile(reader, remoteType+" on "+baseUrl, "json", baseUrl, log);
- if (transformCount == transformerCount())
- {
- successReadingConfig = false;
- }
+ successReadingConfig = false;
}
+ }
- EntityUtils.consume(resEntity);
- }
- catch (IOException e)
- {
- throw new AlfrescoRuntimeException("Failed to read the returned content from "+
- remoteType+" on " + url, e);
- }
+ EntityUtils.consume(resEntity);
}
- else
+ catch (IOException e)
{
- String message = getErrorMessage(resEntity);
- throw new AlfrescoRuntimeException(remoteType+" on " + url+" returned a " + statusCode +
- " status " + message);
+ throw new AlfrescoRuntimeException("Failed to read the returned content from "+
+ remoteType+" on " + url, e);
}
}
else
{
- throw new AlfrescoRuntimeException(remoteType+" on " + url+" did not return an entity " + url);
+ String message = getErrorMessage(resEntity);
+ throw new AlfrescoRuntimeException(remoteType+" on " + url+" returned a " + statusCode +
+ " status " + message);
}
}
- catch (IOException e)
+ else
{
- throw new AlfrescoRuntimeException("Failed to connect or to read the response from "+remoteType+
- " on " + url, e);
+ throw new AlfrescoRuntimeException(remoteType+" on " + url+" did not return an entity " + url);
}
}
catch (IOException e)
{
- throw new AlfrescoRuntimeException(remoteType+" on " + url+" failed to create an HttpClient", e);
+ throw new AlfrescoRuntimeException("Failed to connect or to read the response from "+remoteType+
+ " on " + url, e);
}
+
}
catch (AlfrescoRuntimeException e)
{
diff --git a/repository/src/main/resources/alfresco/rendition-services2-context.xml b/repository/src/main/resources/alfresco/rendition-services2-context.xml
index 6581c6aeb5..a24f3c8c7e 100644
--- a/repository/src/main/resources/alfresco/rendition-services2-context.xml
+++ b/repository/src/main/resources/alfresco/rendition-services2-context.xml
@@ -107,6 +107,13 @@
+
+
+
+
+
+
+
@@ -118,7 +125,8 @@
-
+
+
diff --git a/repository/src/main/resources/alfresco/repository.properties b/repository/src/main/resources/alfresco/repository.properties
index adf594204c..ae82113d9e 100644
--- a/repository/src/main/resources/alfresco/repository.properties
+++ b/repository/src/main/resources/alfresco/repository.properties
@@ -749,6 +749,15 @@ encryption.ssl.truststore.type=JCEKS
# configuration via metadata is deprecated
encryption.ssl.truststore.keyMetaData.location=
+## HttpClient config for Transform Service
+#enable mtls in HttpClientFactory for Transform Service.
+httpclient.config.transform.mTLSEnabled=false
+httpclient.config.transform.maxTotalConnections=40
+httpclient.config.transform.maxHostConnections=40
+httpclient.config.transform.socketTimeout=5000
+httpclient.config.transform.connectionRequestTimeout=5000
+httpclient.config.transform.connectionTimeout=5000
+
# Re-encryptor properties
encryption.reencryptor.chunkSize=100
encryption.reencryptor.numThreads=2
diff --git a/repository/src/test/java/org/alfresco/MTLSTestSuite.java b/repository/src/test/java/org/alfresco/MTLSTestSuite.java
new file mode 100644
index 0000000000..a66438aa65
--- /dev/null
+++ b/repository/src/test/java/org/alfresco/MTLSTestSuite.java
@@ -0,0 +1,39 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2022 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;
+
+import org.alfresco.repo.security.mtls.LocalTransformClientWithMTLSIntegrationTest;
+import org.junit.experimental.categories.Categories;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith (Categories.class)
+@Suite.SuiteClasses({
+ LocalTransformClientWithMTLSIntegrationTest.class
+})
+public class MTLSTestSuite
+{
+}
diff --git a/repository/src/test/java/org/alfresco/repo/content/transform/RemoteTransformerClientTest.java b/repository/src/test/java/org/alfresco/repo/content/transform/RemoteTransformerClientTest.java
index c346ad4007..d9834b2015 100644
--- a/repository/src/test/java/org/alfresco/repo/content/transform/RemoteTransformerClientTest.java
+++ b/repository/src/test/java/org/alfresco/repo/content/transform/RemoteTransformerClientTest.java
@@ -25,6 +25,7 @@
*/
package org.alfresco.repo.content.transform;
+import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.Pair;
@@ -79,7 +80,7 @@ public class RemoteTransformerClientTest
@Mock private StatusLine mockStatusLine;
@Mock private HttpEntity mockReqEntity;
- @Spy private RemoteTransformerClient remoteTransformerClient = new RemoteTransformerClient("TRANSFORMER", "http://localhost:1234/test");
+ @Spy private RemoteTransformerClient remoteTransformerClient = new RemoteTransformerClient("TRANSFORMER", "http://localhost:1234/test", new HttpClientConfig());
private String sourceMimetype = "application/msword";
private String sourceExtension = "doc";
diff --git a/repository/src/test/java/org/alfresco/repo/security/mtls/LocalTransformClientWithMTLSIntegrationTest.java b/repository/src/test/java/org/alfresco/repo/security/mtls/LocalTransformClientWithMTLSIntegrationTest.java
new file mode 100644
index 0000000000..32aa5a820b
--- /dev/null
+++ b/repository/src/test/java/org/alfresco/repo/security/mtls/LocalTransformClientWithMTLSIntegrationTest.java
@@ -0,0 +1,53 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2022 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.repo.security.mtls;
+
+
+import org.alfresco.repo.rendition2.LocalTransformClientIntegrationTest;
+import org.alfresco.repo.rendition2.RenditionService2;
+import org.junit.BeforeClass;
+
+/**
+ * Integration tests for {@link RenditionService2} with mtls enabled
+ */
+public class LocalTransformClientWithMTLSIntegrationTest extends LocalTransformClientIntegrationTest
+{
+ @BeforeClass
+ public static void before()
+ {
+ local();
+
+ System.setProperty("localTransform.core-aio.url", "https://localhost:8090/");
+ System.setProperty("httpclient.config.transform.mTLSEnabled", "true");
+ System.setProperty("ssl-keystore.password", "password");
+ System.setProperty("ssl-truststore.password", "password");
+ System.setProperty("metadata-keystore.password", "password");
+ System.setProperty("metadata-keystore.aliases", "metadata");
+ System.setProperty("metadata-keystore.metadata.password", "password");
+ }
+
+}
diff --git a/repository/src/test/java/org/alfresco/transform/registry/LocalTransformServiceRegistryConfigTest.java b/repository/src/test/java/org/alfresco/transform/registry/LocalTransformServiceRegistryConfigTest.java
index 52a2e4d373..14ed9b8e95 100644
--- a/repository/src/test/java/org/alfresco/transform/registry/LocalTransformServiceRegistryConfigTest.java
+++ b/repository/src/test/java/org/alfresco/transform/registry/LocalTransformServiceRegistryConfigTest.java
@@ -2,7 +2,7 @@
* #%L
* Alfresco Repository
* %%
- * Copyright (C) 2005 - 2022 Alfresco Software Limited
+ * 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
@@ -26,6 +26,12 @@
package org.alfresco.transform.registry;
import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.alfresco.encryption.KeyResourceLoader;
+import org.alfresco.encryption.ssl.SSLEncryptionParameters;
+import org.alfresco.httpclient.GetRequest;
+import org.alfresco.httpclient.HttpClient4Factory;
+import org.alfresco.httpclient.HttpClientConfig;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.transform.AbstractLocalTransform;
import org.alfresco.repo.content.transform.LocalPipelineTransform;
@@ -38,15 +44,23 @@ import org.alfresco.transform.config.TransformOption;
import org.alfresco.transform.config.TransformOptionGroup;
import org.alfresco.transform.config.TransformOptionValue;
import org.alfresco.transform.config.Transformer;
+import org.alfresco.util.ApplicationContextHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.quartz.CronExpression;
+import org.springframework.context.ApplicationContext;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
import java.io.IOException;
import java.util.ArrayList;
@@ -265,7 +279,7 @@ public class LocalTransformServiceRegistryConfigTest extends TransformRegistryMo
*/
private void retrieveLocalTransformList(String path)
{
- CombinedConfig combinedConfig = new CombinedConfig(log, registry);
+ CombinedConfig combinedConfig = new CombinedConfig(log, registry, registry.getHttpClientConfig());
combinedConfig.addLocalConfig(path);
combinedConfig.register(registry);
@@ -388,7 +402,7 @@ public class LocalTransformServiceRegistryConfigTest extends TransformRegistryMo
private void register(String path) throws IOException
{
- CombinedConfig combinedConfig = new CombinedConfig(log, registry);
+ CombinedConfig combinedConfig = new CombinedConfig(log, registry, registry.getHttpClientConfig());
combinedConfig.addLocalConfig(path);
combinedConfig.register((TransformServiceRegistryImpl)registry);
}
@@ -928,4 +942,5 @@ public class LocalTransformServiceRegistryConfigTest extends TransformRegistryMo
-1,"image/png", Collections.emptyMap(), null);
assertNotNull("Should supported csv to png", pipelineTransform);
}
+
}
diff --git a/repository/src/test/resources/test/alfresco/test-renditions-context.xml b/repository/src/test/resources/test/alfresco/test-renditions-context.xml
index 855d93a1e7..990b71bf97 100644
--- a/repository/src/test/resources/test/alfresco/test-renditions-context.xml
+++ b/repository/src/test/resources/test/alfresco/test-renditions-context.xml
@@ -8,6 +8,13 @@
parent="baseTransformationRenderingEngine">
+
+
+
+
+
+
+
@@ -20,6 +27,7 @@
+
\ No newline at end of file
diff --git a/scripts/ci/docker-compose/docker-compose.yaml b/scripts/ci/docker-compose/docker-compose.yaml
index c14fc91734..053a0bb320 100644
--- a/scripts/ci/docker-compose/docker-compose.yaml
+++ b/scripts/ci/docker-compose/docker-compose.yaml
@@ -10,7 +10,7 @@ services:
- "8090:8090"
postgres:
image: postgres:14.4
- profiles: ["default", "with-transform-core-aio", "postgres"]
+ profiles: ["default", "with-transform-core-aio", "postgres", "with-mtls-transform-core-aio"]
environment:
- POSTGRES_PASSWORD=alfresco
- POSTGRES_USER=alfresco
@@ -19,8 +19,41 @@ services:
ports:
- "5433:5432"
activemq:
- profiles: ["default", "with-transform-core-aio", "activemq"]
+ profiles: ["default", "with-transform-core-aio", "activemq", "with-mtls-transform-core-aio"]
image: alfresco/alfresco-activemq:5.17.1-jre11-rockylinux8
ports:
- "5672:5672" # AMQP
- - "61616:61616" # OpenWire
\ No newline at end of file
+ - "61616:61616" # OpenWire
+ mtls-transform-core-aio:
+ profiles: ["with-mtls-transform-core-aio"]
+ image: quay.io/alfresco/alfresco-transform-core-aio:${TRANSFORMERS_TAG}
+ hostname: transform-core-aio
+ ports:
+ - 8090:8090
+ volumes:
+ - ${GITHUB_WORKSPACE}/keystores/tengineAIO/tengineAIO.truststore:/tengineAIO.truststore
+ - ${GITHUB_WORKSPACE}/keystores/tengineAIO/tengineAIO.keystore:/tengineAIO.keystore
+ environment:
+ ACTIVEMQ_URL: "nio://activemq:61616"
+ ACTIVEMQ_USER: "admin"
+ ACTIVEMQ_PASSWORD: "admin"
+ LOG_LEVEL: debug
+
+ SERVER_SSL_ENABLED: "true"
+ SERVER_SSL_KEY_PASSWORD: "password"
+ SERVER_SSL_KEY_STORE: "file:/tengineAIO.keystore"
+ SERVER_SSL_KEY_STORE_PASSWORD: "password"
+ SERVER_SSL_KEY_STORE_TYPE: "JCEKS"
+
+ SERVER_SSL_CLIENT_AUTH: "need"
+ SERVER_SSL_TRUST_STORE: "file:/tengineAIO.truststore"
+ SERVER_SSL_TRUST_STORE_PASSWORD: "password"
+ SERVER_SSL_TRUST_STORE_TYPE: "JCEKS"
+
+ CLIENT_SSL_KEY_STORE: "file:/tengineAIO.keystore"
+ CLIENT_SSL_KEY_STORE_PASSWORD: "password"
+ CLIENT_SSL_KEY_STORE_TYPE: "JCEKS"
+
+ CLIENT_SSL_TRUST_STORE: "file:/tengineAIO.truststore"
+ CLIENT_SSL_TRUST_STORE_PASSWORD: "password"
+ CLIENT_SSL_TRUST_STORE_TYPE: "JCEKS"
\ No newline at end of file
diff --git a/scripts/ci/generate_keystores.sh b/scripts/ci/generate_keystores.sh
new file mode 100644
index 0000000000..cf37ea857e
--- /dev/null
+++ b/scripts/ci/generate_keystores.sh
@@ -0,0 +1,27 @@
+#! /bin/bash
+#! /bin/bash
+
+# SETTINGS
+# Alfresco Format: "classic" / "current" is supported only from 7.0
+ALFRESCO_FORMAT=current
+
+#Contains directory settings
+source ${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/utils.sh
+
+# Cleanup previous output of script
+rm -rd $CA_DIR
+rm -rd $KEYSTORES_DIR
+rm -rd $CERTIFICATES_DIR
+
+# SETTINGS
+# Alfresco Format: "classic" / "current" is supported only from 7.0
+ALFRESCO_FORMAT=current
+
+#CA
+${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/run_ca.sh -keysize 2048 -keystorepass password -certdname "/C=GB/ST=UK/L=Maidenhead/O=Alfresco Software Ltd./OU=Unknown/CN=Custom Alfresco CA" -servername localhost -validityduration 1
+#Alfresco
+${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/run_additional.sh -servicename alfresco -rootcapass password -keysize 2048 -keystoretype JCEKS -keystorepass password -truststoretype JCEKS -truststorepass password -certdname "/C=GB/ST=UK/L=Maidenhead/O=Alfresco Software Ltd./OU=Unknown/CN=Custom Alfresco Repository" -servername localhost -alfrescoformat $ALFRESCO_FORMAT
+#Alfresco Metadata encryption
+${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/run_encryption.sh -subfoldername alfresco -servicename encryption -encstorepass mp6yc0UD9e -encmetadatapass oKIWzVdEdA -alfrescoformat $ALFRESCO_FORMAT
+#T-Engine AIO
+${GITHUB_WORKSPACE}/alfresco-ssl-generator/ssl-tool/run_additional.sh -servicename tengineAIO -rootcapass password -keysize 2048 -keystoretype JCEKS -keystorepass password -truststoretype JCEKS -truststorepass password -certdname "/C=GB/ST=UK/L=Maidenhead/O=Alfresco Software Ltd./OU=Unknown/CN=T-Engine AIO" -servername localhost -alfrescoformat $ALFRESCO_FORMAT
\ No newline at end of file