From bb15d7ecaacf44b147c5a1efeaa9005e1f7005bc Mon Sep 17 00:00:00 2001 From: Vladimir Sitnikov Date: Tue, 21 Jul 2020 21:40:58 +0300 Subject: [PATCH 1/2] Allow clients to customize s3mock server TLS certificate This enables to use a regular S3 client with full TLS verification --- .../testing/s3mock/S3MockApplication.java | 41 ++++++- .../src/main/resources/application.properties | 7 +- .../testsupport/common/S3MockStarter.java | 25 +++- .../junit5/sdk1/CustomCertificateTest.java | 115 ++++++++++++++++++ .../junit5/src/test/resources/customcert.jks | Bin 0 -> 2631 bytes 5 files changed, 180 insertions(+), 8 deletions(-) create mode 100644 testsupport/junit5/src/test/java/com/adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java create mode 100644 testsupport/junit5/src/test/resources/customcert.jks diff --git a/server/src/main/java/com/adobe/testing/s3mock/S3MockApplication.java b/server/src/main/java/com/adobe/testing/s3mock/S3MockApplication.java index 6b8f0eeae..3acc75e38 100644 --- a/server/src/main/java/com/adobe/testing/s3mock/S3MockApplication.java +++ b/server/src/main/java/com/adobe/testing/s3mock/S3MockApplication.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Adobe. + * Copyright 2017-2020 Adobe. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -82,6 +82,11 @@ public class S3MockApplication { public static final int DEFAULT_HTTP_PORT = 9090; public static final int RANDOM_PORT = 0; + public static final String DEFAULT_SERVER_SSL_KEY_STORE = "classpath:s3mock.jks"; + public static final String DEFAULT_SERVER_SSL_KEY_STORE_PASSWORD = "password"; + public static final String DEFAULT_SERVER_SSL_KEY_ALIAS = "selfsigned"; + public static final String DEFAULT_SERVER_SSL_KEY_PASSWORD = "password"; + /** * Property name for passing a comma separated list of buckets that are to be created at startup. */ @@ -104,6 +109,30 @@ public class S3MockApplication { */ public static final String PROP_HTTP_PORT = "http.port"; + /** + * Property name for passing the path to the keystore to use. + * Defaults to {@value DEFAULT_SERVER_SSL_KEY_STORE}. + */ + public static final String SERVER_SSL_KEY_STORE = "server.ssl.key-store"; + + /** + * Property name for passing the password for the keystore. + * Defaults to {@value DEFAULT_SERVER_SSL_KEY_STORE_PASSWORD}. + */ + public static final String SERVER_SSL_KEY_STORE_PASSWORD = "server.ssl.key-store-password"; + + /** + * Property name for specifying the key to use. + * Defaults to {@value DEFAULT_SERVER_SSL_KEY_ALIAS}. + */ + public static final String SERVER_SSL_KEY_ALIAS = "server.ssl.key-alias"; + + /** + * Property name for passing the password for the key. + * Defaults to {@value DEFAULT_SERVER_SSL_KEY_PASSWORD}. + */ + public static final String SERVER_SSL_KEY_PASSWORD = "server.ssl.key-password"; + /** * Property name for using either HTTPS or HTTP connections. */ @@ -163,8 +192,14 @@ public static S3MockApplication start(final Map properties, final String... args) { final Map defaults = new HashMap<>(); - defaults.put(S3MockApplication.PROP_HTTPS_PORT, DEFAULT_HTTPS_PORT); - defaults.put(S3MockApplication.PROP_HTTP_PORT, DEFAULT_HTTP_PORT); + defaults.put(PROP_HTTPS_PORT, DEFAULT_HTTPS_PORT); + defaults.put(PROP_HTTP_PORT, DEFAULT_HTTP_PORT); + + // Specify the default SSL parameters here. Users can override them + defaults.put(SERVER_SSL_KEY_STORE, DEFAULT_SERVER_SSL_KEY_STORE); + defaults.put(SERVER_SSL_KEY_STORE_PASSWORD, DEFAULT_SERVER_SSL_KEY_STORE_PASSWORD); + defaults.put(SERVER_SSL_KEY_ALIAS, DEFAULT_SERVER_SSL_KEY_ALIAS); + defaults.put(SERVER_SSL_KEY_PASSWORD, DEFAULT_SERVER_SSL_KEY_PASSWORD); Banner.Mode bannerMode = Banner.Mode.CONSOLE; diff --git a/server/src/main/resources/application.properties b/server/src/main/resources/application.properties index b9341d33a..6c97ccbae 100644 --- a/server/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -1,5 +1,5 @@ # -# Copyright 2017-2019 Adobe. +# Copyright 2017-2020 Adobe. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,9 +14,8 @@ # limitations under the License. # -server.ssl.key-store=classpath:s3mock.jks -server.ssl.key-store-password=password -server.ssl.key-password=password +# server.ssl.* are moved to S3MockApplication#start so users can override them +# The values in application.properties can't be overriden by SpringApplicationBuilder#properties logging.level.org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver=ERROR logging.level.org.eclipse.jetty.util.ssl.SslContextFactory.config=ERROR diff --git a/testsupport/common/src/main/java/com/adobe/testing/s3mock/testsupport/common/S3MockStarter.java b/testsupport/common/src/main/java/com/adobe/testing/s3mock/testsupport/common/S3MockStarter.java index 54f958a73..c65c4337f 100644 --- a/testsupport/common/src/main/java/com/adobe/testing/s3mock/testsupport/common/S3MockStarter.java +++ b/testsupport/common/src/main/java/com/adobe/testing/s3mock/testsupport/common/S3MockStarter.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 Adobe. + * Copyright 2017-2020 Adobe. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -227,6 +227,11 @@ public abstract static class BaseBuilder { protected final Map arguments = new HashMap<>(); + public BaseBuilder withProperty(String name, String value) { + arguments.put(name, value); + return this; + } + public BaseBuilder withInitialBuckets(final String... initialBuckets) { arguments.put(S3MockApplication.PROP_INITIAL_BUCKETS, join(",", initialBuckets)); return this; @@ -252,6 +257,24 @@ public BaseBuilder withSecureConnection(final boolean secureConnection) { return this; } + /** + * Configures SSL parameters for the mock server. + * @param keyStore value for server.ssl.key-store + * @param keyStorePassword value for server.ssl.key-store-password + * @param keyAlias value for server.ssl.key-alias + * @param keyPassword value for server.ssl.key-password + * @return this builder + */ + public BaseBuilder withSslParameters( + String keyStore, String keyStorePassword, String keyAlias, String keyPassword + ) { + arguments.put(S3MockApplication.SERVER_SSL_KEY_STORE, keyStore); + arguments.put(S3MockApplication.SERVER_SSL_KEY_STORE_PASSWORD, keyStorePassword); + arguments.put(S3MockApplication.SERVER_SSL_KEY_ALIAS, keyAlias); + arguments.put(S3MockApplication.SERVER_SSL_KEY_PASSWORD, keyPassword); + return this; + } + /** * Reduces logging level WARN and suppresses the startup banner. * diff --git a/testsupport/junit5/src/test/java/com/adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java b/testsupport/junit5/src/test/java/com/adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java new file mode 100644 index 000000000..d5bb17e5d --- /dev/null +++ b/testsupport/junit5/src/test/java/com/adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2020 Adobe. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.adobe.testing.s3mock.junit5.sdk1; + +import com.adobe.testing.s3mock.junit5.S3MockExtension; +import com.amazonaws.ClientConfiguration; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.client.builder.AwsClientBuilder; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import java.io.InputStream; +import java.security.KeyStore; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; + +/** + * Test ensures there's a way to configure user-provided certificate for S3Mock. + * + *

The certificate was created with {@code keytool -genkey -keyalg RSA -alias customcert + * -keystore customcert.jks -storepass qwerty -keysize 2048 -ext san=dns:localhost}

+ */ +@Execution(ExecutionMode.SAME_THREAD) +public class CustomCertificateTest { + private static final String KEYSTORE_FILE_NAME = "customcert.jks"; + private static final String KEYSTORE_PASSWORD = "qwerty"; + private static final String KEY_ALIAS = "customcert"; + + @RegisterExtension + public static final S3MockExtension s3mock = + S3MockExtension.builder() + .withSslParameters("classpath:" + KEYSTORE_FILE_NAME, KEYSTORE_PASSWORD, KEY_ALIAS, + KEYSTORE_PASSWORD) + .build(); + + @Test + void connectWithCustomSSLContext() throws Exception { + // We use regular Amazon S3 API to ensure it would be able to connect to S3 mock server + // with no hacks like "allow any certificate" + + // Note: we still have to configure ClientConfiguration as there's no reliable way + // to adjust the system-default TrustManager in the runtime. + // An alternative approach is to use javax.net.ssl.truststore properties + // at the Java process startup. + + AmazonS3 s3 = AmazonS3ClientBuilder.standard() + .withCredentials(new AWSStaticCredentialsProvider( + new BasicAWSCredentials("foo", "bar"))) + .withClientConfiguration(createClientConfiguration(KEYSTORE_FILE_NAME)) + .withEndpointConfiguration( + new AwsClientBuilder.EndpointConfiguration( + "https://localhost:" + s3mock.getPort(), + "us-west-1")) + .enablePathStyleAccess() + .build(); + + // Below is a smoke-test of the API. The point is to check if SSL connectivity works + String bucketName = "non-existent-bucket-to-verify-if-api-works"; + Assertions.assertFalse(s3.doesBucketExistV2(bucketName), + () -> "Bucket " + bucketName + " must not be present at the mock server"); + + s3.shutdown(); + } + + private static ClientConfiguration createClientConfiguration(String keystoreFileName) + throws Exception { + // It configures Apache Http Client to use our own trust store (==trusted certificate) + ClientConfiguration clientConfiguration = new ClientConfiguration(); + clientConfiguration.getApacheHttpClientConfig() + .setSslSocketFactory(new SSLConnectionSocketFactory(createSslContext(keystoreFileName))); + return clientConfiguration; + } + + /** + * Load a certificate from a given keystore and generate a {@code SSLContext} that trusts the + * certificate. + * + * @param keystoreFileName keystore name to use + * @return SSLContext + * @throws Exception in case something fails + */ + private static SSLContext createSslContext(String keystoreFileName) throws Exception { + KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream jks = + CustomCertificateTest.class.getResourceAsStream("/" + keystoreFileName)) { + ks.load(jks, KEYSTORE_PASSWORD.toCharArray()); + } + TrustManagerFactory tmf = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ks); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, tmf.getTrustManagers(), null); + return sslContext; + } +} diff --git a/testsupport/junit5/src/test/resources/customcert.jks b/testsupport/junit5/src/test/resources/customcert.jks new file mode 100644 index 0000000000000000000000000000000000000000..924e87f0a976df6b09601557201084fe767953c3 GIT binary patch literal 2631 zcmY+EcRUmhAII-F+{t!A$tdHDa+h(&bzEeWy$W~s4%y>MxHy|!b`lZE$X=Iuc~I(X z&T0@vwz7Hrp4apIey`_`?|6MapYQk2H=Mw%K?kIV6Cj6-5GkS#afcPi1k5KuRzL*E z&r|t3oWS7we-?uSh`?ZTDqEeF1>>3jUa>F(>GBDn12_S+1D9lE{-1pM{5%Lc3x#^i zw~7k>yj?S(nH=0ClB2&tM-QMFfe4^piyD6KzzXIIy5$aGt_M-bS?eA_wi%7hG(0F# zdDq6jjq~LbescSyQh@=KGF(^u{;urhs{AC$xMv+@EkoKIb zttnBo$THfHi**>f_u;410=K_CQU|{O_}D_s+=tiD!|#VN{xd#YI(c`Fjjp6=+4Y9_p;ebJkL~=? z@z%m+kCB739h-A0bE%_SxmLd<{5f)w^M54jbmo+aoT5@F7RJO!^Wp@@H8uj{K{HRB z-n*>PN%B(<5y%u~U@lEZgkD8zQK-^RqJZ-?Rq-v|_L`eiIj>FG6n;n6(KXxf!|SJ8 zu&g=MqCX$S#bRFh1uzp4rJ!-YNUN^9T8OE@m&H(;(E2}OVzHepYJAZ!n zktvtPwJ+}E9IM2cc$#V9Gutgjxv90GA9m|oV3Jqh81u(`jK`^ zls%4w7Sf?6M*i@tW=rDOq=d>5zN||)IO~me6^x zgwh=l+Pn4JxSwNKhLgkfVOy^Y^_s1dgpl(_Wmd+C^-2O$KZtuPBYJv1d7i-7P zaZC#r$BUp>EB9t7M(&i7!hQXO0ASACIh(hEmSMIFw^Ny2plSycIcqhWPo*{wZe9Wl{J68>@uh>8 z5p`=^r;5}-Zrpq=A!17s7+{+Dt8Rfk1PC)00=;xnAWAYd+o%kETV$ljei4@tlr;aN z^$1+BY|w5nr?r-FVk#4TQv7Lf@d>SQ?kZ|SPg}F{t&MWQrcy5aqTD;0{mXQv$oX|sT9D7un(_QJ%eqexS{KHm$Q<8jC zZP_r!XM4#JQ=cK}uYJ4l@|l+&Z2V-tnrXRtCwS1dyS@ikxWB>z_huJ;~ z3~rxd*hJk}aIS;*zIp>VI!aiqJKI@C-3i>QH7d%JzAi83Hp)wT(V>(rKAUgy9-#xj z^fxLYP{dh5D8LaA2=E640PX?2PIqU3A0PlO@=u5d0R?fHxqCSaBNUVsPLy!1ByE zee`V>nnJ7ZpBk>x@m#yxjaAKk2PyWDYXyC3U%2`cq&YaG-Ec`bWAQv4b&3GuM(W5x zFSf`t3EZz}vWqgMk`Sb{mJ@2;eKX9>*D=Maum;|ir=8JxaIuXj3^YFknDHuxD{VWD z>)h_+2#=A8PXXUW)YJxora9-fS9#43(Iiz>pa@Cr@? z$z1Fmpw<>8WKlaT8 zP?tCP6w1=Cb?}E+wXOZl=Ruq}hmw^Bf5>NMwD(cy98Jb%RmaJIx^12(NIJsz zMTYCgk$)%v1_WiOjWp}briy!ub!;lx_;soIZ<`W9t!chR@?$jWRzFrLwiKB&Xt!8v z1QRhc7?Fpm&QCq;F>QW$oYBNuZl6rRcUMgb;sWpUjDJyiW6Acb_E9l*cT6Y!dpeN^ zRzeuCo4B{u>3@PXAviyGwc_otLeHKAQy#S5P*WsyW5*G`@A~FV`XVl) zmReqM%Sb7S(!vbnN7ttyWn)!6j{SVr`oQ+L&(O0r-7|yWI$v>uT;(HY-)?smudHX+ zF1AQfw0BIF&XH7(f>W_6UaDwZ-b~_>VQTN*)AZzG%b6k3YK^1= Date: Tue, 11 Aug 2020 10:51:22 +0300 Subject: [PATCH 2/2] Try 2017-2020 copyright --- .../adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsupport/junit5/src/test/java/com/adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java b/testsupport/junit5/src/test/java/com/adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java index d5bb17e5d..1abb9ed08 100644 --- a/testsupport/junit5/src/test/java/com/adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java +++ b/testsupport/junit5/src/test/java/com/adobe/testing/s3mock/junit5/sdk1/CustomCertificateTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Adobe. + * Copyright 2017-2020 Adobe. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License.