mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
[ACS-4459] Investigate and extend/universalize current custom Solr mTLS implementation in Repository (#1735)
* ACS-4459 Add new HttpClient Factory for Mutual TLS and implement it for Transform Service * ACS-4462 Add e2e for MTLS
This commit is contained in:
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
@@ -137,6 +137,10 @@
|
||||
<artifactId>commons-dbcp2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
202
core/src/main/java/org/alfresco/httpclient/HttpClientConfig.java
Normal file
202
core/src/main/java/org/alfresco/httpclient/HttpClientConfig.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, String> 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<String, String> 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<String, String> 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<String> extractValueFromConfig(HttpClientPropertiesEnum property)
|
||||
{
|
||||
Optional<String> 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<String> supportedProperties = new ArrayList<>();
|
||||
|
||||
static {
|
||||
for (HttpClientPropertiesEnum property : HttpClientPropertiesEnum.values()) {
|
||||
supportedProperties.add(property.name);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPropertyNameSupported(String propertyName) {
|
||||
return supportedProperties.contains(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.alfresco.httpclient;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
|
||||
public class HttpClientException extends AlfrescoRuntimeException
|
||||
{
|
||||
public HttpClientException(String msgId)
|
||||
{
|
||||
super(msgId);
|
||||
}
|
||||
}
|
@@ -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<TransformOption> 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();
|
||||
|
@@ -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<String, Set<String>> 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<String> 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)
|
||||
{
|
||||
|
@@ -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<Boolean, String> 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))
|
||||
{
|
||||
|
@@ -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,31 +91,31 @@ public class CombinedConfig extends CombinedTransformConfig
|
||||
return configFileFinder.readFiles(path, log);
|
||||
}
|
||||
|
||||
public boolean addRemoteConfig(List<String> urls, String remoteType)
|
||||
public boolean addRemoteConfig(List<String> urls, String remoteType) throws IOException
|
||||
{
|
||||
try(CloseableHttpClient httpclient = HttpClient4Factory.createHttpClient(httpClientConfig))
|
||||
{
|
||||
boolean successReadingConfig = true;
|
||||
for (String url : urls)
|
||||
{
|
||||
if (addRemoteConfig(url, remoteType))
|
||||
if (addRemoteConfig(httpclient, url, remoteType))
|
||||
{
|
||||
tEngineCount++;
|
||||
}
|
||||
else
|
||||
} else
|
||||
{
|
||||
successReadingConfig = false;
|
||||
}
|
||||
}
|
||||
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))
|
||||
{
|
||||
@@ -164,11 +168,7 @@ public class CombinedConfig extends CombinedTransformConfig
|
||||
throw new AlfrescoRuntimeException("Failed to connect or to read the response from "+remoteType+
|
||||
" on " + url, e);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException(remoteType+" on " + url+" failed to create an HttpClient", e);
|
||||
}
|
||||
|
||||
}
|
||||
catch (AlfrescoRuntimeException e)
|
||||
{
|
||||
|
@@ -107,6 +107,13 @@
|
||||
<!-- Replaced in the enterprise edition -->
|
||||
<bean id="remoteTransformServiceRegistry" class="org.alfresco.repo.content.transform.DummyTransformServiceRegistry" />
|
||||
|
||||
<bean id="httpClientConfigTransform" class="org.alfresco.httpclient.HttpClientConfig" init-method="init" >
|
||||
<property name="sslEncryptionParameters" ref="sslEncryptionParameters" />
|
||||
<property name="keyResourceLoader" ref="springKeyResourceLoader" />
|
||||
<property name="properties" ref="global-properties" />
|
||||
<property name="serviceName" value="transform" />
|
||||
</bean>
|
||||
|
||||
<bean id="localTransformServiceRegistry" class="org.alfresco.repo.content.transform.LocalTransformServiceRegistry" >
|
||||
<property name="jsonObjectMapper" ref="localTransformServiceRegistryJsonObjectMapper" />
|
||||
<property name="pipelineConfigDir" value="${local.transform.pipeline.config.dir}" />
|
||||
@@ -118,7 +125,8 @@
|
||||
<property name="mimetypeService" ref="MimetypeService" />
|
||||
<property name="strictMimeTypeCheck" value="${transformer.strict.mimetype.check}"/>
|
||||
<property name="retryTransformOnDifferentMimeType" value="${content.transformer.retryOn.different.mimetype}"/>
|
||||
<property name="shutdownIndicator" ref="shutdownIndicator"></property>
|
||||
<property name="shutdownIndicator" ref="shutdownIndicator" />
|
||||
<property name="httpClientConfig" ref="httpClientConfigTransform" />
|
||||
</bean>
|
||||
|
||||
<bean id="localTransformServiceRegistryJsonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper" />
|
||||
|
@@ -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
|
||||
|
39
repository/src/test/java/org/alfresco/MTLSTestSuite.java
Normal file
39
repository/src/test/java/org/alfresco/MTLSTestSuite.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #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
|
||||
{
|
||||
}
|
@@ -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";
|
||||
|
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
* #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");
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -8,6 +8,13 @@
|
||||
parent="baseTransformationRenderingEngine">
|
||||
</bean>
|
||||
|
||||
<bean id="httpClientConfigTransform" class="org.alfresco.httpclient.HttpClientConfig" init-method="init" >
|
||||
<property name="sslEncryptionParameters" ref="sslEncryptionParameters" />
|
||||
<property name="keyResourceLoader" ref="springKeyResourceLoader" />
|
||||
<property name="properties" ref="global-properties" />
|
||||
<property name="serviceName" value="transform" />
|
||||
</bean>
|
||||
|
||||
<!-- Keep it simple. Disable retries when the mimetype is wrong and we can transform what it is actually -->
|
||||
<bean id="localTransformServiceRegistry" class="org.alfresco.repo.content.transform.LocalTransformServiceRegistry" >
|
||||
<property name="jsonObjectMapper" ref="localTransformServiceRegistryJsonObjectMapper" />
|
||||
@@ -20,6 +27,7 @@
|
||||
<property name="mimetypeService" ref="MimetypeService" />
|
||||
<property name="strictMimeTypeCheck" value="${transformer.strict.mimetype.check}"/>
|
||||
<property name="retryTransformOnDifferentMimeType" value="false"/>
|
||||
<property name="httpClientConfig" ref="httpClientConfigTransform" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
@@ -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
|
||||
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"
|
27
scripts/ci/generate_keystores.sh
Normal file
27
scripts/ci/generate_keystores.sh
Normal file
@@ -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
|
Reference in New Issue
Block a user