From 29c157f6f9bddf03392b9e12124340594867b5b3 Mon Sep 17 00:00:00 2001
From: petruki <31597636+petruki@users.noreply.github.com>
Date: Sun, 2 Jul 2023 13:55:03 -0700
Subject: [PATCH] Added support to custom SSL conext - switcher.truststore
---
README.md | 2 +
pom.xml | 8 +-
.../switcherapi/client/ContextBuilder.java | 10 +++
.../client/SwitcherProperties.java | 22 +++++
.../switcherapi/client/model/ContextKey.java | 12 ++-
.../client/remote/ClientWSBuilder.java | 50 ++++++++++++
.../client/remote/ClientWSImpl.java | 3 +-
.../service/remote/SwitcherRemoteService.java | 2 +-
.../client/remote/ClientWSBuilderTest.java | 75 ++++++++++++++++++
src/test/resources/keystore.jks | Bin 0 -> 2324 bytes
10 files changed, 176 insertions(+), 8 deletions(-)
create mode 100644 src/main/java/com/github/switcherapi/client/remote/ClientWSBuilder.java
create mode 100644 src/test/java/com/github/switcherapi/client/remote/ClientWSBuilderTest.java
create mode 100644 src/test/resources/keystore.jks
diff --git a/README.md b/README.md
index 10fa16f1..27ae8db4 100644
--- a/README.md
+++ b/README.md
@@ -77,6 +77,8 @@ switcher.snapshot.skipvalidation -> true/false Skip snapshotValidation() that ca
switcher.snapshot.updateinterval -> Enable the Snapshot Auto Update given an interval of time - e.g. 1s (s: seconds, m: minutes)
switcher.silent -> true/false Contingency in case of some problem with connectivity with the API
switcher.retry -> Time given to the module to re-establish connectivity with the API - e.g. 5s (s: seconds - m: minutes - h: hours)
+switcher.truststore.path -> Path to the truststore file
+switcher.truststore.password -> Truststore password
(Java 8 applications only)
switcher.regextimeout -> Time in ms given to Timed Match Worker used for offline Regex (ReDoS safety mechanism) - 3000 default value
diff --git a/pom.xml b/pom.xml
index cbac6b43..74648cf0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,9 +51,9 @@
${java.version}
- 2.39
- 2.39
- 2.39
+ 2.40
+ 2.40
+ 2.40
2.10.1
@@ -63,7 +63,7 @@
5.0.0-alpha.11
- 5.9.2
+ 5.9.3
1.9.1
diff --git a/src/main/java/com/github/switcherapi/client/ContextBuilder.java b/src/main/java/com/github/switcherapi/client/ContextBuilder.java
index 16c64a71..8909c71a 100644
--- a/src/main/java/com/github/switcherapi/client/ContextBuilder.java
+++ b/src/main/java/com/github/switcherapi/client/ContextBuilder.java
@@ -110,4 +110,14 @@ public ContextBuilder offlineMode(boolean offlineMode) {
return this;
}
+ public ContextBuilder truststorePath(String truststorePath) {
+ properties.setTruststorePath(truststorePath);
+ return this;
+ }
+
+ public ContextBuilder truststorePassword(String truststorePassword) {
+ properties.setTruststorePassword(truststorePassword);
+ return this;
+ }
+
}
diff --git a/src/main/java/com/github/switcherapi/client/SwitcherProperties.java b/src/main/java/com/github/switcherapi/client/SwitcherProperties.java
index e513913c..bb030f8b 100644
--- a/src/main/java/com/github/switcherapi/client/SwitcherProperties.java
+++ b/src/main/java/com/github/switcherapi/client/SwitcherProperties.java
@@ -51,6 +51,10 @@ class SwitcherProperties {
private boolean offlineMode;
+ private String truststorePath;
+
+ private String truststorePassword;
+
public SwitcherProperties() {
this.environment = DEFAULT_ENV;
this.regexTimeout = DEFAULT_REGEX_TIMEOUT;
@@ -72,6 +76,8 @@ public void loadFromProperties(Properties prop) {
setOfflineMode(Boolean.parseBoolean(SwitcherUtils.resolveProperties(ContextKey.OFFLINE_MODE.getParam(), prop)));
setRetryAfter(SwitcherUtils.resolveProperties(ContextKey.RETRY_AFTER.getParam(), prop));
setRegexTimeout(SwitcherUtils.resolveProperties(ContextKey.REGEX_TIMEOUT.getParam(), prop));
+ setTruststorePath(SwitcherUtils.resolveProperties(ContextKey.TRUSTSTORE_PATH.getParam(), prop));
+ setTruststorePassword(SwitcherUtils.resolveProperties(ContextKey.TRUSTSTORE_PASSWORD.getParam(), prop));
}
public T getValue(ContextKey contextKey, Class type) {
@@ -209,4 +215,20 @@ public void setOfflineMode(boolean offlineMode) {
this.offlineMode = offlineMode;
}
+ public String getTruststorePath() {
+ return truststorePath;
+ }
+
+ public void setTruststorePath(String truststorePath) {
+ this.truststorePath = truststorePath;
+ }
+
+ public String getTruststorePassword() {
+ return truststorePassword;
+ }
+
+ public void setTruststorePassword(String truststorePassword) {
+ this.truststorePassword = truststorePassword;
+ }
+
}
diff --git a/src/main/java/com/github/switcherapi/client/model/ContextKey.java b/src/main/java/com/github/switcherapi/client/model/ContextKey.java
index 02ed0d62..3046614b 100644
--- a/src/main/java/com/github/switcherapi/client/model/ContextKey.java
+++ b/src/main/java/com/github/switcherapi/client/model/ContextKey.java
@@ -82,7 +82,17 @@ public enum ContextKey {
/**
* (Number) Defines the Timed Match regex time out.
*/
- REGEX_TIMEOUT("switcher.regextimeout", "regexTimeout");
+ REGEX_TIMEOUT("switcher.regextimeout", "regexTimeout"),
+
+ /**
+ * (Path) Defines the path for the trustsore file.
+ */
+ TRUSTSTORE_PATH("switcher.truststore.path", "truststorePath"),
+
+ /**
+ * (String) Defines the password for the truststore file.
+ */
+ TRUSTSTORE_PASSWORD("switcher.truststore.password", "truststorePassword");
private final String param;
private final String propField;
diff --git a/src/main/java/com/github/switcherapi/client/remote/ClientWSBuilder.java b/src/main/java/com/github/switcherapi/client/remote/ClientWSBuilder.java
new file mode 100644
index 00000000..d6c9f9a2
--- /dev/null
+++ b/src/main/java/com/github/switcherapi/client/remote/ClientWSBuilder.java
@@ -0,0 +1,50 @@
+package com.github.switcherapi.client.remote;
+
+import com.github.switcherapi.client.SwitcherContextBase;
+import com.github.switcherapi.client.exception.SwitcherException;
+import com.github.switcherapi.client.model.ContextKey;
+import org.apache.commons.lang3.StringUtils;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManagerFactory;
+import javax.ws.rs.client.ClientBuilder;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.security.KeyStore;
+
+public class ClientWSBuilder {
+
+ private static final String KEYSTORE_TYPE = "JKS";
+
+ private static final String PROTOCOL = "TLSv1.2";
+
+ private ClientWSBuilder() {
+ throw new IllegalStateException("Utility class");
+ }
+
+ public static ClientBuilder builder() {
+ if (StringUtils.isNotBlank(SwitcherContextBase.contextStr(ContextKey.TRUSTSTORE_PATH))) {
+ return builderSSL();
+ }
+
+ return ClientBuilder.newBuilder();
+ }
+
+ public static ClientBuilder builderSSL() {
+ try (InputStream readStream = new FileInputStream(SwitcherContextBase.contextStr(ContextKey.TRUSTSTORE_PATH))) {
+ final KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE);
+ trustStore.load(readStream, SwitcherContextBase.contextStr(ContextKey.TRUSTSTORE_PASSWORD).toCharArray());
+
+ final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(trustStore);
+
+ final SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
+ sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
+
+ return ClientBuilder.newBuilder().sslContext(sslContext);
+ } catch (Exception e) {
+ throw new SwitcherException("Error while building SSL context", e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java b/src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java
index ae233a6b..3be88f06 100644
--- a/src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java
+++ b/src/main/java/com/github/switcherapi/client/remote/ClientWSImpl.java
@@ -14,7 +14,6 @@
import org.apache.logging.log4j.Logger;
import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
@@ -41,7 +40,7 @@ public class ClientWSImpl implements ClientWS {
private final Client client;
public ClientWSImpl() {
- this.client = ClientBuilder.newClient();
+ this.client = ClientWSBuilder.builder().build();
}
@Override
diff --git a/src/main/java/com/github/switcherapi/client/service/remote/SwitcherRemoteService.java b/src/main/java/com/github/switcherapi/client/service/remote/SwitcherRemoteService.java
index 1f2feec8..a3ade720 100644
--- a/src/main/java/com/github/switcherapi/client/service/remote/SwitcherRemoteService.java
+++ b/src/main/java/com/github/switcherapi/client/service/remote/SwitcherRemoteService.java
@@ -47,7 +47,7 @@ public CriteriaResponse executeCriteria(final Switcher switcher) {
return response;
} catch (final SwitcherRemoteException e) {
- logger.error("Failed to execute criteria - {}", e.getMessage());
+ logger.error("Failed to execute criteria - {}\nCause: {}", e.getMessage(), e.getCause());
return executeSilentCriteria(switcher, e);
}
}
diff --git a/src/test/java/com/github/switcherapi/client/remote/ClientWSBuilderTest.java b/src/test/java/com/github/switcherapi/client/remote/ClientWSBuilderTest.java
new file mode 100644
index 00000000..8e50aae6
--- /dev/null
+++ b/src/test/java/com/github/switcherapi/client/remote/ClientWSBuilderTest.java
@@ -0,0 +1,75 @@
+package com.github.switcherapi.client.remote;
+
+import com.github.switcherapi.client.ContextBuilder;
+import com.github.switcherapi.client.SwitcherContextBase;
+import com.github.switcherapi.client.exception.SwitcherException;
+import org.junit.jupiter.api.Test;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.client.ClientBuilder;
+import java.util.Objects;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ClientWSBuilderTest {
+
+ @Test
+ void shouldCreateClientBuilder() {
+ // given
+ SwitcherContextBase.configure(ContextBuilder.builder()
+ .truststorePath("")
+ .truststorePassword(""));
+
+ // test
+ ClientBuilder clientBuilder = ClientWSBuilder.builder();
+ assertNotNull(clientBuilder);
+
+ SSLContext sslContext = clientBuilder.build().getSslContext();
+ assertNotNull(sslContext);
+ assertEquals("TLS", sslContext.getProtocol());
+ }
+
+ @Test
+ void shouldCreateClientBuilderSSL() {
+ // given
+ String truststorePath = Objects.requireNonNull(getClass().getClassLoader()
+ .getResource("keystore.jks")).getPath();
+
+ SwitcherContextBase.configure(ContextBuilder.builder()
+ .truststorePath(truststorePath)
+ .truststorePassword("changeit"));
+
+ // test
+ ClientBuilder clientBuilder = ClientWSBuilder.builder();
+ assertNotNull(clientBuilder);
+
+ SSLContext sslContext = clientBuilder.build().getSslContext();
+ assertNotNull(sslContext);
+ assertEquals("TLSv1.2", sslContext.getProtocol());
+ }
+
+ @Test
+ void shouldNotCreateClientBuilderSSL_invalidKeystorePassword() {
+ // given
+ String truststorePath = Objects.requireNonNull(getClass().getClassLoader()
+ .getResource("keystore.jks")).getPath();
+
+ SwitcherContextBase.configure(ContextBuilder.builder()
+ .truststorePath(truststorePath)
+ .truststorePassword("INVALID"));
+
+ // test
+ assertThrows(SwitcherException.class, ClientWSBuilder::builder);
+ }
+
+ @Test
+ void shouldNotCreateClientBuilderSSL_invalidKeystorePath() {
+ // given
+ SwitcherContextBase.configure(ContextBuilder.builder()
+ .truststorePath("INVALID")
+ .truststorePassword("changeit"));
+
+ // test
+ assertThrows(SwitcherException.class, ClientWSBuilder::builder);
+ }
+}
diff --git a/src/test/resources/keystore.jks b/src/test/resources/keystore.jks
new file mode 100644
index 0000000000000000000000000000000000000000..75709948af71c23a5c57c22fe91bd3baf6e02114
GIT binary patch
literal 2324
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