This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push: new 8c9fa4657e [ENHANCEMENT] Allow to trust all certificates with S3 blobstore (#1846) 8c9fa4657e is described below commit 8c9fa4657e3ddbd85013bc6d57fa690b07e3f8fe Author: Benoit TELLIER <btell...@linagora.com> AuthorDate: Mon Dec 11 07:38:35 2023 +0100 [ENHANCEMENT] Allow to trust all certificates with S3 blobstore (#1846) --- .../modules/ROOT/pages/configure/blobstore.adoc | 4 ++ .../objectstorage/aws/AwsS3AuthConfiguration.java | 30 ++++++-- .../blob/objectstorage/aws/S3BlobStoreDAO.java | 23 +++++++ .../aws/s3/AwsS3ConfigurationReader.java | 2 + .../aws/s3/AwsS3ConfigurationReaderTest.java | 79 ++++++++++++++++++++++ src/site/xdoc/server/config-blobstore.xml | 4 ++ 6 files changed, 138 insertions(+), 4 deletions(-) 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 1b5f267f21..709325a76c 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 @@ Maximum size of stored objects expressed in bytes. | 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 ecc4014027..70975d73d7 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 @@ public class AwsS3AuthConfiguration { 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 @@ public class AwsS3AuthConfiguration { 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 @@ public class AwsS3AuthConfiguration { 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 @@ public class AwsS3AuthConfiguration { 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 @@ public class AwsS3AuthConfiguration { 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 @@ public class AwsS3AuthConfiguration { 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 @@ public class AwsS3AuthConfiguration { this.trustStoreType = trustStoreType; this.trustStoreSecret = trustStoreSecret; this.trustStoreAlgorithm = trustStoreAlgorithm; + this.trustAll = trustAll; } public URI getEndpoint() { @@ -173,6 +188,10 @@ public class AwsS3AuthConfiguration { return trustStoreAlgorithm; } + public boolean isTrustAll() { + return trustAll; + } + @Override public final boolean equals(Object o) { if (o instanceof AwsS3AuthConfiguration) { @@ -183,7 +202,8 @@ public class AwsS3AuthConfiguration { 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 @@ public class AwsS3AuthConfiguration { @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 @@ public class AwsS3AuthConfiguration { .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 725cf8dcd8..b24460176f 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.io.InputStream; 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 java.util.concurrent.CompletableFuture; 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.PutObjectResponse; 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 @@ public class S3BlobStoreDAO implements BlobStoreDAO, Startable, Closeable { } 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 50ba125a59..a7618eee5c 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 @@ public class AwsS3ConfigurationReader { 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 @@ public class AwsS3ConfigurationReader { .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 21edf20283..2d675a86bc 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 @@ class AwsS3ConfigurationReaderTest { 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 4ff8da2850..7583ba493e 100644 --- a/src/site/xdoc/server/config-blobstore.xml +++ b/src/site/xdoc/server/config-blobstore.xml @@ -184,6 +184,10 @@ generate salt with : openssl rand -hex 16 <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> --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org