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

Reply via email to