[ENHANCEMENT] Allow to trust all certificates with S3 blobstore (#1846)
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/blobstore.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/blobstore.adoc
index 1b5f267..709325a 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/blobstore.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/blobstore.adoc
@@ -143,6 +143,10 @@
| objectstorage.s3.truststore.algorithm
| optional: Use this specific trust store algorithm; default SunX509
+| objectstorage.s3.trustall
+| optional: boolean. Defaults to false. Cannot be set to true with other trustore options. Wether James should validate
+S3 endpoint SSL certificates.
+
| objectstorage.s3.read.timeout
| optional: HTTP read timeout. duration, default value being second. Leaving it empty relies on S3 driver defaults.
diff --git a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3AuthConfiguration.java b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3AuthConfiguration.java
index ecc4014..70975d7 100644
--- a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3AuthConfiguration.java
+++ b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/AwsS3AuthConfiguration.java
@@ -53,6 +53,7 @@
private final URI endpoint;
private final String accessKeyId;
private final String secretKey;
+ private Optional<Boolean> trustAll;
private Optional<String> trustStorePath;
private Optional<String> trustStoreType;
@@ -67,6 +68,7 @@
this.trustStoreType = Optional.empty();
this.trustStoreSecret = Optional.empty();
this.trustStoreAlgorithm = Optional.empty();
+ this.trustAll = Optional.empty();
}
public ReadyToBuild trustStorePath(Optional<String> trustStorePath) {
@@ -87,6 +89,11 @@
return trustStoreType(Optional.ofNullable(trustStoreType));
}
+ public ReadyToBuild trustAll(boolean trustAll) {
+ this.trustAll = Optional.of(trustAll);
+ return this;
+ }
+
public ReadyToBuild trustStoreSecret(Optional<String> trustStoreSecret) {
this.trustStoreSecret = trustStoreSecret;
return this;
@@ -114,8 +121,13 @@
Preconditions.checkNotNull(secretKey, "'secretKey' is mandatory");
Preconditions.checkArgument(!secretKey.isEmpty(), "'secretKey' is mandatory");
+ boolean trustAll = this.trustAll.orElse(false);
+ Preconditions.checkState(!(trustAll && trustStoreType.isPresent()), "Cannot specify 'trustAll' and 'trustStoreType' simultaneously");
+ Preconditions.checkState(!(trustAll && trustStorePath.isPresent()), "Cannot specify 'trustAll' and 'trustStorePath' simultaneously");
+ Preconditions.checkState(!(trustAll && trustStoreSecret.isPresent()), "Cannot specify 'trustAll' and 'trustStoreSecret' simultaneously");
+
return new AwsS3AuthConfiguration(endpoint, accessKeyId, secretKey,
- trustStorePath, trustStoreType, trustStoreSecret, trustStoreAlgorithm);
+ trustStorePath, trustStoreType, trustStoreSecret, trustStoreAlgorithm, trustAll);
}
}
}
@@ -128,6 +140,7 @@
private final Optional<String> trustStoreType;
private final Optional<String> trustStoreSecret;
private final Optional<String> trustStoreAlgorithm;
+ private final boolean trustAll;
private AwsS3AuthConfiguration(URI endpoint,
String accessKeyId,
@@ -135,7 +148,8 @@
Optional<String> trustStorePath,
Optional<String> trustStoreType,
Optional<String> trustStoreSecret,
- Optional<String> trustStoreAlgorithm) {
+ Optional<String> trustStoreAlgorithm,
+ boolean trustAll) {
this.endpoint = endpoint;
this.accessKeyId = accessKeyId;
this.secretKey = secretKey;
@@ -143,6 +157,7 @@
this.trustStoreType = trustStoreType;
this.trustStoreSecret = trustStoreSecret;
this.trustStoreAlgorithm = trustStoreAlgorithm;
+ this.trustAll = trustAll;
}
public URI getEndpoint() {
@@ -173,6 +188,10 @@
return trustStoreAlgorithm;
}
+ public boolean isTrustAll() {
+ return trustAll;
+ }
+
@Override
public final boolean equals(Object o) {
if (o instanceof AwsS3AuthConfiguration) {
@@ -183,7 +202,8 @@
Objects.equal(trustStorePath, that.trustStorePath) &&
Objects.equal(trustStoreType, that.trustStoreType) &&
Objects.equal(trustStoreSecret, that.trustStoreSecret) &&
- Objects.equal(trustStoreAlgorithm, that.trustStoreAlgorithm);
+ Objects.equal(trustStoreAlgorithm, that.trustStoreAlgorithm) &&
+ Objects.equal(trustAll, that.trustAll);
}
return false;
}
@@ -191,7 +211,8 @@
@Override
public final int hashCode() {
return Objects.hashCode(endpoint, accessKeyId, secretKey,
- trustStorePath, trustStoreType, trustStoreSecret, trustStoreAlgorithm);
+ trustStorePath, trustStoreType, trustStoreSecret, trustStoreAlgorithm,
+ trustAll);
}
@Override
@@ -203,6 +224,7 @@
.add("trustStorePath", trustStorePath)
.add("trustStoreSecret", trustStoreSecret)
.add("trustStoreAlgorithm", trustStoreAlgorithm)
+ .add("trustAll", trustAll)
.toString();
}
}
diff --git a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
index 725cf8d..b244601 100644
--- a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
+++ b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
@@ -28,6 +28,7 @@
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
+import java.security.cert.X509Certificate;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
@@ -35,7 +36,9 @@
import javax.annotation.PreDestroy;
import javax.inject.Inject;
+import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
import org.apache.commons.io.IOUtils;
import org.apache.james.blob.api.BlobId;
@@ -83,6 +86,23 @@
import software.amazon.awssdk.services.s3.model.S3Object;
public class S3BlobStoreDAO implements BlobStoreDAO, Startable, Closeable {
+ private static final TrustManager DUMMY_TRUST_MANAGER = new X509TrustManager() {
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) {
+ // Always trust
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) {
+ // Always trust
+ }
+ };
+
private static class FileBackedOutputStreamByteSource extends ByteSource {
private final FileBackedOutputStream stream;
private final long size;
@@ -158,6 +178,9 @@
}
private TlsTrustManagersProvider getTrustManagerProvider(AwsS3AuthConfiguration configuration) {
+ if (configuration.isTrustAll()) {
+ return () -> ImmutableList.of(DUMMY_TRUST_MANAGER).toArray(new TrustManager[0]);
+ }
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
configuration.getTrustStoreAlgorithm().orElse(TrustManagerFactory.getDefaultAlgorithm()));
diff --git a/server/container/guice/blob/s3/src/main/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReader.java b/server/container/guice/blob/s3/src/main/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReader.java
index 50ba125..a7618ee 100644
--- a/server/container/guice/blob/s3/src/main/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReader.java
+++ b/server/container/guice/blob/s3/src/main/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReader.java
@@ -34,6 +34,7 @@
static final String OBJECTSTORAGE_TRUSTSTORE_TYPE = "objectstorage.s3.truststore.type";
static final String OBJECTSTORAGE_TRUSTSTORE_SECRET = "objectstorage.s3.truststore.secret";
static final String OBJECTSTORAGE_TRUSTSTORE_ALGORITHM = "objectstorage.s3.truststore.algorithm";
+ static final String OBJECTSTORAGE_TRUSTALL = "objectstorage.s3.trustall";
public static AwsS3AuthConfiguration from(Configuration configuration) {
String endpoint = configuration.getString(OBJECTSTORAGE_ENDPOINT);
@@ -49,6 +50,7 @@
.trustStoreType(configuration.getString(OBJECTSTORAGE_TRUSTSTORE_TYPE))
.trustStoreSecret(configuration.getString(OBJECTSTORAGE_TRUSTSTORE_SECRET))
.trustStoreAlgorithm(configuration.getString(OBJECTSTORAGE_TRUSTSTORE_ALGORITHM))
+ .trustAll(configuration.getBoolean(OBJECTSTORAGE_TRUSTALL, false))
.build();
}
}
diff --git a/server/container/guice/blob/s3/src/test/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReaderTest.java b/server/container/guice/blob/s3/src/test/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReaderTest.java
index 21edf20..2d675a8 100644
--- a/server/container/guice/blob/s3/src/test/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReaderTest.java
+++ b/server/container/guice/blob/s3/src/test/java/org/apache/james/modules/objectstorage/aws/s3/AwsS3ConfigurationReaderTest.java
@@ -20,6 +20,7 @@
package org.apache.james.modules.objectstorage.aws.s3;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.net.URI;
@@ -89,6 +90,84 @@
assertThat(authConfiguration).isEqualTo(expected);
}
+
+ @Test
+ void trustAllAndTrustStoreShouldBeIncompatible() {
+ Configuration configuration = new PropertiesConfiguration();
+ URI endpoint = URI.create("http://myEndpoint");
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ENDPOINT, endpoint);
+ String accessKeyId = "myAccessKeyId";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ACCESKEYID, accessKeyId);
+ String secretKey = "mySecretKey";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_SECRETKEY, secretKey);
+ String trustStorePath = "/some/where/truststore.p12";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_PATH, trustStorePath);
+ String trustStoreType = "PKCS12";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_TYPE, trustStoreType);
+ String trustStoreSecret = "myTrustStoreSecret";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_SECRET, trustStoreSecret);
+ String trustStoreAlgorithm = "myTrustStoreAlgorithm";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_ALGORITHM, trustStoreAlgorithm);
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTALL, true);
+
+ assertThatThrownBy(() -> AwsS3ConfigurationReader.from(configuration)).isInstanceOf(IllegalStateException.class);
+ }
+
+
+ @Test
+ void trustNotAllAndTrustStoreShouldBeCompatible() {
+ Configuration configuration = new PropertiesConfiguration();
+ URI endpoint = URI.create("http://myEndpoint");
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ENDPOINT, endpoint);
+ String accessKeyId = "myAccessKeyId";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ACCESKEYID, accessKeyId);
+ String secretKey = "mySecretKey";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_SECRETKEY, secretKey);
+ String trustStorePath = "/some/where/truststore.p12";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_PATH, trustStorePath);
+ String trustStoreType = "PKCS12";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_TYPE, trustStoreType);
+ String trustStoreSecret = "myTrustStoreSecret";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_SECRET, trustStoreSecret);
+ String trustStoreAlgorithm = "myTrustStoreAlgorithm";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTSTORE_ALGORITHM, trustStoreAlgorithm);
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTALL, false);
+
+ assertThatCode(() -> AwsS3ConfigurationReader.from(configuration)).doesNotThrowAnyException();
+ }
+
+ @Test
+ void trustAllShouldBeFalseByDefault() {
+ Configuration configuration = new PropertiesConfiguration();
+ URI endpoint = URI.create("http://myEndpoint");
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ENDPOINT, endpoint);
+ String accessKeyId = "myAccessKeyId";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ACCESKEYID, accessKeyId);
+ String secretKey = "mySecretKey";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_SECRETKEY, secretKey);
+
+ AwsS3AuthConfiguration testee = AwsS3ConfigurationReader.from(configuration);
+
+ assertThat(testee.isTrustAll()).isFalse();
+ }
+
+
+ @Test
+ void trustAllShouldBeTrueWhenEnabled() {
+ Configuration configuration = new PropertiesConfiguration();
+ URI endpoint = URI.create("http://myEndpoint");
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ENDPOINT, endpoint);
+ String accessKeyId = "myAccessKeyId";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_ACCESKEYID, accessKeyId);
+ String secretKey = "mySecretKey";
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_SECRETKEY, secretKey);
+ configuration.addProperty(AwsS3ConfigurationReader.OBJECTSTORAGE_TRUSTALL, true);
+
+ AwsS3AuthConfiguration testee = AwsS3ConfigurationReader.from(configuration);
+
+ assertThat(testee.isTrustAll()).isTrue();
+ }
+
@Test
void fromShouldWorkWithoutOptionals() {
Configuration configuration = new PropertiesConfiguration();
diff --git a/src/site/xdoc/server/config-blobstore.xml b/src/site/xdoc/server/config-blobstore.xml
index 4ff8da2..7583ba4 100644
--- a/src/site/xdoc/server/config-blobstore.xml
+++ b/src/site/xdoc/server/config-blobstore.xml
@@ -184,6 +184,10 @@
<dt><strong>objectstorage.s3.truststore.algorithm</strong></dt>
<dd><i>optional:</i> Use this specific trust store algorithm; default SunX509</dd>
+ <dt><strong>objectstorage.s3.trustall</strong></dt>
+ <dd><i>optional:</i> boolean. Defaults to false. Cannot be set to true with other trustore options. Wether James should validate
+ S3 endpoint SSL certificates.</dd>
+
<dt><strong>objectstorage.s3.read.timeout</strong></dt>
<dd><i>optional:</i> HTTP read timeout. duration, default value being second. Leaving it empty relies on S3 driver defaults.</dd>