diff --git a/README.md b/README.md index 2b6527c..f073d01 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ e.g. Key=`ab73kbw90e/diku`, Field=`diku` Configuration information is specified in two forms: 1. System Properties - General configuration -1. Properties File - Configuration specific to the desired secure store +2. Properties File - Configuration specific to the desired secure store ### System Properties @@ -163,15 +163,38 @@ Configuration information is specified in two forms: | `token_cache_capacity` | `100` | Max token cache size | | `log_level` | `INFO` | Log4j Log Level | | `request_timeout_ms` | `30000` | Request Timeout | -| `ssl_enabled` | `false` | Set whether SSL/TLS is enabled for Vertx Http Server | -| `keystore_type` | `NA` | Set the key store type | -| `keystore_provider` | `NA` | Set the provider name of the key store | -| `keystore_path` | `NA` | Set the path to the key store file | -| `keystore_password` | `NA` | Set the password for the key store | -| `key_alias` | `NA` | Optional param that points to a specific key within the key store | -| `key_alias_password` | `NA` | Optional param that points to a password of `key_alias` if it protected | | `api_key_sources` | `PARAM,HEADER,PATH` | Defines the sources (order of precendence) of the API key. | +### System Properties for TLS configuration for Http server +To configure Transport Layer Security (TLS) for the HTTP server in an edge module, the following configuration parameters should be used. +Parameters marked as Required are required only in case when ssl_enabled is set to true. + +| Property | Default | Description | +|-----------------------------------|-------------------|---------------------------------------------------------------------------------------------| +| `http-server.ssl_enabled` | `false` | Set whether SSL/TLS is enabled for Vertx Http Server | +| `http-server.keystore_type` | `NA` | (Required). Set the type of the keystore. Common types include `JKS`, `PKCS12`, and `BCFKS` | +| `http-server.keystore_provider` | `NA` | Set the provider name of the key store | +| `http-server.keystore_path` | `NA` | (Required). Set the location of the keystore file in the local file system | +| `http-server.keystore_password` | `NA` | (Required). Set the password for the keystore | +| `http-server.key_alias` | `NA` | Set the alias of the key within the keystore. | +| `http-server.key_alias_password` | `NA` | Optional param that points to a password of `key_alias` if it protected | + +### System Properties for TLS configuration for Web Client +To configure Transport Layer Security (TLS) for Web clients in the edge module, you can use the following configuration parameters. +Truststore parameters for configuring Web clients are optional even when ssl_enabled = true. +If truststore parameters need to be populated, truststore_type, truststore_path and truststore_password are required. + +| Property | Default | Description | +|-----------------------------------|-------------------|----------------------------------------------------------------------------------| +| `web-client.ssl_enabled` | `false` | Set whether SSL/TLS is enabled for Vertx Http Server | +| `web-client.truststore_type` | `NA` | Set the type of the keystore. Common types include `JKS`, `PKCS12`, and `BCFKS` | +| `web-client.truststore_provider` | `NA` | Set the provider name of the key store | +| `web-client.truststore_path` | `NA` | Set the location of the keystore file in the local file system | +| `web-client.truststore_password` | `NA` | Set the password for the keystore | +| `web-client.key_alias` | `NA` | Set the alias of the key within the keystore. | +| `web-client.key_alias_password` | `NA` | Optional param that points to a password of `key_alias` if it protected | + + ## Additional information There will be a single instance of okapi client per OkapiClientFactory and per tenant, which means that this client should never be closed or else there will be runtime errors. To enforce this behaviour, method close() has been removed from OkapiClient class. diff --git a/src/main/java/org/folio/edge/core/Constants.java b/src/main/java/org/folio/edge/core/Constants.java index 6b93faa..ca092fc 100644 --- a/src/main/java/org/folio/edge/core/Constants.java +++ b/src/main/java/org/folio/edge/core/Constants.java @@ -24,13 +24,23 @@ private Constants() { public static final String SYS_REQUEST_TIMEOUT_MS = "request_timeout_ms"; public static final String SYS_API_KEY_SOURCES = "api_key_sources"; public static final String SYS_RESPONSE_COMPRESSION = "response_compression"; - public static final String SYS_SSL_ENABLED = "ssl_enabled"; - public static final String SYS_KEYSTORE_PATH = "keystore_path"; - public static final String SYS_KEYSTORE_PASSWORD = "keystore_password"; - public static final String SYS_KEYSTORE_TYPE = "keystore_type"; - public static final String SYS_KEYSTORE_PROVIDER = "keystore_provider"; - public static final String SYS_KEY_ALIAS = "key_alias"; - public static final String SYS_KEY_ALIAS_PASSWORD = "key_alias_password"; + + // System properties for SSL/TLS http server configuration + public static final String SYS_HTTP_SERVER_SSL_ENABLED = "http-server.ssl_enabled"; + public static final String SYS_HTTP_SERVER_KEYSTORE_TYPE = "http-server.keystore_type"; + public static final String SYS_HTTP_SERVER_KEYSTORE_PROVIDER = "http-server.keystore_provider"; + public static final String SYS_HTTP_SERVER_KEYSTORE_PATH = "http-server.keystore_path"; + public static final String SYS_HTTP_SERVER_KEYSTORE_PASSWORD = "http-server.keystore_password"; + public static final String SYS_HTTP_SERVER_KEY_ALIAS = "http-server.key_alias"; + public static final String SYS_HTTP_SERVER_KEY_ALIAS_PASSWORD = "http-server.key_alias_password"; + public static final String SYS_WEB_CLIENT_SSL_ENABLED = "web-client.ssl_enabled"; + // System properties for SSL/TLS web client configuration + public static final String SYS_WEB_CLIENT_TRUSTSTORE_TYPE = "web-client.truststore_type"; + public static final String SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER = "web-client.truststore_provider"; + public static final String SYS_WEB_CLIENT_TRUSTSTORE_PATH = "web-client.truststore_path"; + public static final String SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD = "web-client.truststore_password"; + public static final String SYS_WEB_CLIENT_KEY_ALIAS = "web-client.key_alias"; + public static final String SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD = "web-client.key_alias_password"; // Property names public static final String PROP_SECURE_STORE_TYPE = "secureStore.type"; @@ -105,8 +115,11 @@ private Constants() { defaultMap.put(SYS_RESPONSE_COMPRESSION, Boolean.parseBoolean(System.getProperty(SYS_RESPONSE_COMPRESSION, Boolean.toString(DEFAULT_RESPONSE_COMPRESSION)))); - defaultMap.put(SYS_SSL_ENABLED, - Boolean.parseBoolean(System.getProperty(SYS_SSL_ENABLED, + defaultMap.put(SYS_HTTP_SERVER_SSL_ENABLED, + Boolean.parseBoolean(System.getProperty(SYS_HTTP_SERVER_SSL_ENABLED, + Boolean.toString(DEFAULT_SSL_ENABLED)))); + defaultMap.put(SYS_WEB_CLIENT_SSL_ENABLED, + Boolean.parseBoolean(System.getProperty(SYS_WEB_CLIENT_SSL_ENABLED, Boolean.toString(DEFAULT_SSL_ENABLED)))); defaultMap.put(SYS_SECURE_STORE_PROP_FILE, System.getProperty(SYS_SECURE_STORE_PROP_FILE)); diff --git a/src/main/java/org/folio/edge/core/EdgeVerticleHttp.java b/src/main/java/org/folio/edge/core/EdgeVerticleHttp.java index 194fda8..0e947fa 100644 --- a/src/main/java/org/folio/edge/core/EdgeVerticleHttp.java +++ b/src/main/java/org/folio/edge/core/EdgeVerticleHttp.java @@ -1,28 +1,20 @@ package org.folio.edge.core; -import static org.folio.edge.core.Constants.SYS_KEYSTORE_PASSWORD; -import static org.folio.edge.core.Constants.SYS_KEYSTORE_PATH; -import static org.folio.edge.core.Constants.SYS_KEYSTORE_PROVIDER; -import static org.folio.edge.core.Constants.SYS_KEYSTORE_TYPE; -import static org.folio.edge.core.Constants.SYS_KEY_ALIAS; -import static org.folio.edge.core.Constants.SYS_KEY_ALIAS_PASSWORD; import static org.folio.edge.core.Constants.SYS_PORT; import static org.folio.edge.core.Constants.SYS_RESPONSE_COMPRESSION; -import static org.folio.edge.core.Constants.SYS_SSL_ENABLED; import static org.folio.edge.core.Constants.TEXT_PLAIN; -import com.amazonaws.util.StringUtils; import io.vertx.core.Future; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.Promise; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpServer; -import io.vertx.core.net.KeyStoreOptions; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.folio.edge.core.utils.SslConfigurationUtil; /** * Verticle for edge module which starts a HTTP service. @@ -45,7 +37,7 @@ public void start(Promise promise) { serverOptions.setCompressionSupported(isCompressionSupported); // initialize tls/ssl configuration for web server - configureSslIfEnabled(serverOptions); + SslConfigurationUtil.configureSslServerOptionsIfEnabled(config(), serverOptions); final HttpServer server = getVertx().createHttpServer(serverOptions); @@ -65,37 +57,4 @@ protected void handleHealthCheck(RoutingContext ctx) { .putHeader(HttpHeaders.CONTENT_TYPE, TEXT_PLAIN) .end("\"OK\""); } - - private void configureSslIfEnabled(HttpServerOptions serverOptions) { - final boolean isSslEnabled = config().getBoolean(SYS_SSL_ENABLED); - if (isSslEnabled) { - logger.info("Enabling Vertx Http Server with TLS/SSL configuration..."); - serverOptions.setSsl(true); - String keystoreType = config().getString(SYS_KEYSTORE_TYPE); - if (StringUtils.isNullOrEmpty(keystoreType)) { - throw new IllegalStateException("'keystore_type' system param must be specified when ssl_enabled = true"); - } - logger.info("Using {} keystore type for SSL/TLS", keystoreType); - String keystoreProvider = config().getString(SYS_KEYSTORE_PROVIDER); - logger.info("Using {} keystore provider for SSL/TLS", keystoreProvider); - String keystorePath = config().getString(SYS_KEYSTORE_PATH); - if (StringUtils.isNullOrEmpty(keystorePath)) { - throw new IllegalStateException("'keystore_path' system param must be specified when ssl_enabled = true"); - } - String keystorePassword = config().getString(SYS_KEYSTORE_PASSWORD); - if (StringUtils.isNullOrEmpty(keystorePassword)) { - throw new IllegalStateException("'keystore_password' system param must be specified when ssl_enabled = true"); - } - String keyAlias = config().getString(SYS_KEY_ALIAS); - String keyAliasPassword = config().getString(SYS_KEY_ALIAS_PASSWORD); - - serverOptions.setKeyCertOptions(new KeyStoreOptions() - .setType(keystoreType) - .setProvider(keystoreProvider) - .setPath(keystorePath) - .setPassword(keystorePassword) - .setAlias(keyAlias) - .setAliasPassword(keyAliasPassword)); - } - } } diff --git a/src/main/java/org/folio/edge/core/utils/OkapiClient.java b/src/main/java/org/folio/edge/core/utils/OkapiClient.java index 1ff8467..d9ac317 100644 --- a/src/main/java/org/folio/edge/core/utils/OkapiClient.java +++ b/src/main/java/org/folio/edge/core/utils/OkapiClient.java @@ -14,7 +14,7 @@ import com.amazonaws.util.StringUtils; import io.vertx.core.Future; import io.vertx.core.buffer.Buffer; -import io.vertx.core.net.KeyCertOptions; +import io.vertx.core.net.TrustOptions; import io.vertx.ext.web.client.HttpRequest; import io.vertx.ext.web.client.HttpResponse; import io.vertx.ext.web.client.WebClient; @@ -73,14 +73,20 @@ protected OkapiClient(Vertx vertx, String okapiURL, String tenant, int timeout) initDefaultHeaders(); } - protected OkapiClient(Vertx vertx, String okapiURL, String tenant, int timeout, KeyCertOptions keyCertOptions) { + /** + * Create Okapi client configured to work in SSL/TLS mode. + * Trust options can be null. + */ + protected OkapiClient(Vertx vertx, String okapiURL, String tenant, int timeout, TrustOptions trustOptions) { this.vertx = vertx; this.reqTimeout = timeout; this.okapiURL = okapiURL; this.tenant = tenant; WebClientOptions options = initDefaultWebClientOptions(timeout) - .setSsl(true) - .setKeyCertOptions(keyCertOptions); + .setSsl(true); + if (trustOptions != null) { + options.setTrustOptions(trustOptions); + } client = WebClientFactory.getWebClient(vertx, options); initDefaultHeaders(); } diff --git a/src/main/java/org/folio/edge/core/utils/OkapiClientFactory.java b/src/main/java/org/folio/edge/core/utils/OkapiClientFactory.java index 1871af6..2a53a1b 100644 --- a/src/main/java/org/folio/edge/core/utils/OkapiClientFactory.java +++ b/src/main/java/org/folio/edge/core/utils/OkapiClientFactory.java @@ -4,8 +4,7 @@ import java.util.concurrent.ConcurrentHashMap; import io.vertx.core.Vertx; -import io.vertx.core.net.KeyCertOptions; -import io.vertx.core.net.KeyStoreOptions; +import io.vertx.core.net.TrustOptions; public class OkapiClientFactory { @@ -14,7 +13,8 @@ public class OkapiClientFactory { public final String okapiURL; public final Vertx vertx; public final int reqTimeoutMs; - private KeyCertOptions keyCertOptions; + private boolean sslMode; + private TrustOptions trustOptions; public OkapiClientFactory(Vertx vertx, String okapiURL, int reqTimeoutMs) { this.vertx = vertx; @@ -22,30 +22,17 @@ public OkapiClientFactory(Vertx vertx, String okapiURL, int reqTimeoutMs) { this.reqTimeoutMs = reqTimeoutMs; } - public OkapiClientFactory(Vertx vertx, - String okapiURL, - int reqTimeoutMs, - String keystoreType, - String keystoreProvider, - String keystorePath, - String keystorePassword, - String keyAlias, - String keyAliasPassword) { + public OkapiClientFactory(Vertx vertx, String okapiURL, int reqTimeoutMs, TrustOptions trustOptions) { this(vertx, okapiURL, reqTimeoutMs); - this.keyCertOptions = new KeyStoreOptions() - .setType(keystoreType) - .setProvider(keystoreProvider) - .setPath(keystorePath) - .setPassword(keystorePassword) - .setAlias(keyAlias) - .setAliasPassword(keyAliasPassword); + this.sslMode = true; + this.trustOptions = trustOptions; } public OkapiClient getOkapiClient(String tenant) { - if (keyCertOptions == null) { - return cache.computeIfAbsent(tenant, t -> new OkapiClient(vertx, okapiURL, t, reqTimeoutMs)); + if (sslMode) { + return cache.computeIfAbsent(tenant, t -> new OkapiClient(vertx, okapiURL, t, reqTimeoutMs, trustOptions)); } else { - return cache.computeIfAbsent(tenant, t -> new OkapiClient(vertx, okapiURL, t, reqTimeoutMs, keyCertOptions)); + return cache.computeIfAbsent(tenant, t -> new OkapiClient(vertx, okapiURL, t, reqTimeoutMs)); } } } diff --git a/src/main/java/org/folio/edge/core/utils/OkapiClientFactoryInitializer.java b/src/main/java/org/folio/edge/core/utils/OkapiClientFactoryInitializer.java new file mode 100644 index 0000000..d84bb8f --- /dev/null +++ b/src/main/java/org/folio/edge/core/utils/OkapiClientFactoryInitializer.java @@ -0,0 +1,59 @@ +package org.folio.edge.core.utils; + +import static org.folio.edge.core.Constants.SYS_OKAPI_URL; +import static org.folio.edge.core.Constants.SYS_REQUEST_TIMEOUT_MS; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_KEY_ALIAS; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_SSL_ENABLED; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_TRUSTSTORE_PATH; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_TRUSTSTORE_TYPE; + +import com.amazonaws.util.StringUtils; +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.KeyStoreOptions; +import io.vertx.core.net.TrustOptions; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class OkapiClientFactoryInitializer { + private static final Logger logger = LogManager.getLogger(OkapiClientFactoryInitializer.class); + + private OkapiClientFactoryInitializer() { + } + + public static OkapiClientFactory createInstance(Vertx vertx, JsonObject config) { + String okapiUrl = config.getString(SYS_OKAPI_URL); + Integer requestTimeout = config.getInteger(SYS_REQUEST_TIMEOUT_MS); + boolean isSslEnabled = config.getBoolean(SYS_WEB_CLIENT_SSL_ENABLED); + if (isSslEnabled) { + logger.info("Creating OkapiClientFactory with Enhance HTTP Endpoint Security and TLS mode enabled"); + String truststoreType = config.getString(SYS_WEB_CLIENT_TRUSTSTORE_TYPE); + String truststoreProvider = config.getString(SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER); + String truststorePath = config.getString(SYS_WEB_CLIENT_TRUSTSTORE_PATH); + String truststorePassword = config.getString(SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD); + String keyAlias = config.getString(SYS_WEB_CLIENT_KEY_ALIAS); + String keyAliasPassword = config.getString(SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD); + if (!StringUtils.isNullOrEmpty(truststoreType) + && !StringUtils.isNullOrEmpty(truststorePath) + && !StringUtils.isNullOrEmpty(truststorePassword)) { + + logger.info("Web client truststore options for type: {} are set, configuring Web Client with them", truststoreType); + TrustOptions trustOptions = new KeyStoreOptions() + .setType(truststoreType) + .setProvider(truststoreProvider) + .setPath(truststorePath) + .setPassword(truststorePassword) + .setAlias(keyAlias) + .setAliasPassword(keyAliasPassword); + return new OkapiClientFactory(vertx, okapiUrl, requestTimeout, trustOptions); + } else { + return new OkapiClientFactory(vertx, okapiUrl, requestTimeout, null); + } + } else { + return new OkapiClientFactory(vertx, okapiUrl, requestTimeout); + } + } +} diff --git a/src/main/java/org/folio/edge/core/utils/SslConfigurationUtil.java b/src/main/java/org/folio/edge/core/utils/SslConfigurationUtil.java new file mode 100644 index 0000000..b166d3b --- /dev/null +++ b/src/main/java/org/folio/edge/core/utils/SslConfigurationUtil.java @@ -0,0 +1,55 @@ +package org.folio.edge.core.utils; + +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEYSTORE_PASSWORD; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEYSTORE_PATH; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEYSTORE_PROVIDER; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEYSTORE_TYPE; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEY_ALIAS; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEY_ALIAS_PASSWORD; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_SSL_ENABLED; + +import com.amazonaws.util.StringUtils; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.KeyStoreOptions; +import io.vertx.core.net.NetServerOptions; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class SslConfigurationUtil { + private static final Logger logger = LogManager.getLogger(SslConfigurationUtil.class); + + private SslConfigurationUtil() {} + + public static void configureSslServerOptionsIfEnabled(JsonObject config, NetServerOptions serverOptions) { + final boolean isSslEnabled = config.getBoolean(SYS_HTTP_SERVER_SSL_ENABLED); + if (isSslEnabled) { + logger.info("Enabling Vertx Http Server with TLS/SSL configuration..."); + serverOptions.setSsl(true); + String keystoreType = config.getString(SYS_HTTP_SERVER_KEYSTORE_TYPE); + if (StringUtils.isNullOrEmpty(keystoreType)) { + throw new IllegalStateException("'keystore_type' system param must be specified when ssl_enabled = true"); + } + logger.info("Using {} keystore type for SSL/TLS", keystoreType); + String keystoreProvider = config.getString(SYS_HTTP_SERVER_KEYSTORE_PROVIDER); + logger.info("Using {} keystore provider for SSL/TLS", keystoreProvider); + String keystorePath = config.getString(SYS_HTTP_SERVER_KEYSTORE_PATH); + if (StringUtils.isNullOrEmpty(keystorePath)) { + throw new IllegalStateException("'keystore_path' system param must be specified when ssl_enabled = true"); + } + String keystorePassword = config.getString(SYS_HTTP_SERVER_KEYSTORE_PASSWORD); + if (StringUtils.isNullOrEmpty(keystorePassword)) { + throw new IllegalStateException("'keystore_password' system param must be specified when ssl_enabled = true"); + } + String keyAlias = config.getString(SYS_HTTP_SERVER_KEY_ALIAS); + String keyAliasPassword = config.getString(SYS_HTTP_SERVER_KEY_ALIAS_PASSWORD); + + serverOptions.setKeyCertOptions(new KeyStoreOptions() + .setType(keystoreType) + .setProvider(keystoreProvider) + .setPath(keystorePath) + .setPassword(keystorePassword) + .setAlias(keyAlias) + .setAliasPassword(keyAliasPassword)); + } + } +} diff --git a/src/test/java/org/folio/edge/core/EdgeVerticleSslTest.java b/src/test/java/org/folio/edge/core/EdgeVerticleSslTest.java index 7b7ca50..1d09837 100644 --- a/src/test/java/org/folio/edge/core/EdgeVerticleSslTest.java +++ b/src/test/java/org/folio/edge/core/EdgeVerticleSslTest.java @@ -1,14 +1,14 @@ package org.folio.edge.core; -import static org.folio.edge.core.Constants.SYS_KEYSTORE_PASSWORD; -import static org.folio.edge.core.Constants.SYS_KEYSTORE_PATH; -import static org.folio.edge.core.Constants.SYS_KEYSTORE_TYPE; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEYSTORE_PASSWORD; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEYSTORE_PATH; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_KEYSTORE_TYPE; import static org.folio.edge.core.Constants.SYS_LOG_LEVEL; import static org.folio.edge.core.Constants.SYS_OKAPI_URL; import static org.folio.edge.core.Constants.SYS_PORT; import static org.folio.edge.core.Constants.SYS_REQUEST_TIMEOUT_MS; import static org.folio.edge.core.Constants.SYS_SECURE_STORE_PROP_FILE; -import static org.folio.edge.core.Constants.SYS_SSL_ENABLED; +import static org.folio.edge.core.Constants.SYS_HTTP_SERVER_SSL_ENABLED; import static org.mockito.Mockito.spy; import io.vertx.core.DeploymentOptions; @@ -45,7 +45,7 @@ public void tearDownOnce() { @Test public void setupSslConfigWithoutType(TestContext context) throws Exception { JsonObject config = getCommonConfig() - .put(SYS_SSL_ENABLED, true); + .put(SYS_HTTP_SERVER_SSL_ENABLED, true); thrown.expect(IllegalStateException.class); thrown.expectMessage("'keystore_type' system param must be specified when ssl_enabled = true"); @@ -56,8 +56,8 @@ public void setupSslConfigWithoutType(TestContext context) throws Exception { @Test public void setupSslConfigWithoutPath(TestContext context) throws Exception { JsonObject config = getCommonConfig() - .put(SYS_SSL_ENABLED, true) - .put(SYS_KEYSTORE_TYPE, "JKS"); + .put(SYS_HTTP_SERVER_SSL_ENABLED, true) + .put(SYS_HTTP_SERVER_KEYSTORE_TYPE, "JKS"); thrown.expect(IllegalStateException.class); thrown.expectMessage("'keystore_path' system param must be specified when ssl_enabled = true"); @@ -68,9 +68,9 @@ public void setupSslConfigWithoutPath(TestContext context) throws Exception { @Test public void setupSslConfigWithoutPassword(TestContext context) throws Exception { JsonObject config = getCommonConfig() - .put(SYS_SSL_ENABLED, true) - .put(SYS_KEYSTORE_TYPE, "JKS") - .put(SYS_KEYSTORE_PATH, "sample_keystore.jks"); + .put(SYS_HTTP_SERVER_SSL_ENABLED, true) + .put(SYS_HTTP_SERVER_KEYSTORE_TYPE, "JKS") + .put(SYS_HTTP_SERVER_KEYSTORE_PATH, "sample_keystore.jks"); thrown.expect(IllegalStateException.class); thrown.expectMessage("'keystore_password' system param must be specified when ssl_enabled = true"); @@ -81,10 +81,10 @@ public void setupSslConfigWithoutPassword(TestContext context) throws Exception @Test public void setupSslConfigWitInvalidPath(TestContext context) throws Exception { JsonObject config = getCommonConfig() - .put(SYS_SSL_ENABLED, true) - .put(SYS_KEYSTORE_TYPE, "JKS") - .put(SYS_KEYSTORE_PATH, "some_keystore_path") - .put(SYS_KEYSTORE_PASSWORD, "password"); + .put(SYS_HTTP_SERVER_SSL_ENABLED, true) + .put(SYS_HTTP_SERVER_KEYSTORE_TYPE, "JKS") + .put(SYS_HTTP_SERVER_KEYSTORE_PATH, "some_keystore_path") + .put(SYS_HTTP_SERVER_KEYSTORE_PASSWORD, "password"); thrown.expect(FileSystemException.class); thrown.expectMessage("Unable to read file at path 'some_keystore_path'"); @@ -95,10 +95,10 @@ public void setupSslConfigWitInvalidPath(TestContext context) throws Exception { @Test public void setupSslConfigWithNotValidPassword(TestContext context) throws Exception { JsonObject config = getCommonConfig() - .put(SYS_SSL_ENABLED, true) - .put(SYS_KEYSTORE_TYPE, "JKS") - .put(SYS_KEYSTORE_PATH, "sample_keystore.jks") - .put(SYS_KEYSTORE_PASSWORD, "not_valid_password"); + .put(SYS_HTTP_SERVER_SSL_ENABLED, true) + .put(SYS_HTTP_SERVER_KEYSTORE_TYPE, "JKS") + .put(SYS_HTTP_SERVER_KEYSTORE_PATH, "sample_keystore.jks") + .put(SYS_HTTP_SERVER_KEYSTORE_PASSWORD, "not_valid_password"); thrown.expect(IOException.class); thrown.expectMessage("keystore password was incorrect"); @@ -109,10 +109,10 @@ public void setupSslConfigWithNotValidPassword(TestContext context) throws Excep @Test public void setupCorrectSslConfig(TestContext context) throws Exception { JsonObject config = getCommonConfig() - .put(SYS_SSL_ENABLED, true) - .put(SYS_KEYSTORE_TYPE, "JKS") - .put(SYS_KEYSTORE_PATH, "sample_keystore.jks") - .put(SYS_KEYSTORE_PASSWORD, "password"); + .put(SYS_HTTP_SERVER_SSL_ENABLED, true) + .put(SYS_HTTP_SERVER_KEYSTORE_TYPE, "JKS") + .put(SYS_HTTP_SERVER_KEYSTORE_PATH, "sample_keystore.jks") + .put(SYS_HTTP_SERVER_KEYSTORE_PASSWORD, "password"); deployVerticle(context, config); } diff --git a/src/test/java/org/folio/edge/core/OkapiClientInitializerTest.java b/src/test/java/org/folio/edge/core/OkapiClientInitializerTest.java new file mode 100644 index 0000000..346f2c4 --- /dev/null +++ b/src/test/java/org/folio/edge/core/OkapiClientInitializerTest.java @@ -0,0 +1,85 @@ +package org.folio.edge.core; + +import static org.folio.edge.core.Constants.SYS_OKAPI_URL; +import static org.folio.edge.core.Constants.SYS_REQUEST_TIMEOUT_MS; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_KEY_ALIAS; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_SSL_ENABLED; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_TRUSTSTORE_PATH; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER; +import static org.folio.edge.core.Constants.SYS_WEB_CLIENT_TRUSTSTORE_TYPE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import io.vertx.core.Vertx; +import io.vertx.core.json.JsonObject; +import io.vertx.core.net.KeyStoreOptions; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.folio.edge.core.utils.OkapiClient; +import org.folio.edge.core.utils.OkapiClientFactory; +import org.folio.edge.core.utils.OkapiClientFactoryInitializer; +import org.junit.Test; + +public class OkapiClientInitializerTest { + private static final String OKAPI_URL = "http://mocked.okapi:9130"; + private static final Integer REQ_TIMEOUT_MS = 5000; + private static final String TRUSTSTORE_TYPE = "some_keystore_type"; + private static final String TRUSTSTORE_PROVIDER = "some_keystore_provider"; + private static final String TRUSTSTORE_PATH = "some_keystore_path"; + private static final String TRUSTSTORE_PASSWORD = "some_keystore_password"; + private static final String KEY_ALIAS = "some_key_alias"; + private static final String KEY_ALIAS_PASSWORD = "some_key_alias_password"; + + @Test + public void testGetOkapiClientFactory() throws IllegalAccessException { + Vertx vertx = Vertx.vertx(); + JsonObject config = new JsonObject() + .put(SYS_OKAPI_URL, OKAPI_URL) + .put(SYS_REQUEST_TIMEOUT_MS, REQ_TIMEOUT_MS) + .put(SYS_WEB_CLIENT_SSL_ENABLED, false); + OkapiClientFactory ocf = OkapiClientFactoryInitializer.createInstance(vertx, config); + + String okapiUrl = (String) FieldUtils.readDeclaredField(ocf, "okapiURL"); + Integer reqTimeoutMs = (Integer) FieldUtils.readDeclaredField(ocf, "reqTimeoutMs"); + KeyStoreOptions keyStoreOptions = (KeyStoreOptions) FieldUtils.readDeclaredField(ocf, "trustOptions", true); + + assertEquals(OKAPI_URL, okapiUrl); + assertEquals(REQ_TIMEOUT_MS, reqTimeoutMs); + assertNull(keyStoreOptions); + OkapiClient client = ocf.getOkapiClient("tenant"); + assertNotNull(client); + } + + @Test + public void testGetSecuredOkapiClientFactory() throws IllegalAccessException { + Vertx vertx = Vertx.vertx(); + JsonObject config = new JsonObject() + .put(SYS_OKAPI_URL, OKAPI_URL) + .put(SYS_REQUEST_TIMEOUT_MS, REQ_TIMEOUT_MS) + .put(SYS_WEB_CLIENT_SSL_ENABLED, true) + .put(SYS_WEB_CLIENT_TRUSTSTORE_TYPE, TRUSTSTORE_TYPE) + .put(SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER, TRUSTSTORE_PROVIDER) + .put(SYS_WEB_CLIENT_TRUSTSTORE_PATH, TRUSTSTORE_PATH) + .put(SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD, TRUSTSTORE_PASSWORD) + .put(SYS_WEB_CLIENT_KEY_ALIAS, KEY_ALIAS) + .put(SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD, KEY_ALIAS_PASSWORD); + OkapiClientFactory ocf = OkapiClientFactoryInitializer.createInstance(vertx, config); + + String okapiUrl = (String) FieldUtils.readDeclaredField(ocf, "okapiURL"); + Integer reqTimeoutMs = (Integer) FieldUtils.readDeclaredField(ocf, "reqTimeoutMs"); + KeyStoreOptions keyStoreOptions = (KeyStoreOptions) FieldUtils.readDeclaredField(ocf, "trustOptions", true); + + assertEquals(OKAPI_URL, okapiUrl); + assertEquals(REQ_TIMEOUT_MS, reqTimeoutMs); + assertEquals(TRUSTSTORE_TYPE, keyStoreOptions.getType()); + assertEquals(TRUSTSTORE_PROVIDER, keyStoreOptions.getProvider()); + assertEquals(TRUSTSTORE_PATH, keyStoreOptions.getPath()); + assertEquals(TRUSTSTORE_PASSWORD, keyStoreOptions.getPassword()); + assertEquals(KEY_ALIAS, keyStoreOptions.getAlias()); + assertEquals(KEY_ALIAS_PASSWORD, keyStoreOptions.getAliasPassword()); + OkapiClient client = ocf.getOkapiClient("tenant"); + assertNotNull(client); + } +} diff --git a/src/test/java/org/folio/edge/core/utils/OkapiClientFactoryTest.java b/src/test/java/org/folio/edge/core/utils/OkapiClientFactoryTest.java index faf5c27..0a86a99 100644 --- a/src/test/java/org/folio/edge/core/utils/OkapiClientFactoryTest.java +++ b/src/test/java/org/folio/edge/core/utils/OkapiClientFactoryTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertNull; import io.vertx.core.net.KeyStoreOptions; +import io.vertx.core.net.TrustOptions; import org.apache.commons.lang3.reflect.FieldUtils; import org.junit.Test; @@ -28,7 +29,7 @@ public void testGetOkapiClientFactory() throws IllegalAccessException { String okapiUrl = (String) FieldUtils.readDeclaredField(ocf, "okapiURL"); Integer reqTimeoutMs = (Integer) FieldUtils.readDeclaredField(ocf, "reqTimeoutMs"); - KeyStoreOptions keyStoreOptions = (KeyStoreOptions) FieldUtils.readDeclaredField(ocf, "keyCertOptions", true); + KeyStoreOptions keyStoreOptions = (KeyStoreOptions) FieldUtils.readDeclaredField(ocf, "trustOptions", true); assertEquals(OKAPI_URL, okapiUrl); assertEquals(REQ_TIMEOUT_MS, reqTimeoutMs); @@ -40,12 +41,18 @@ public void testGetOkapiClientFactory() throws IllegalAccessException { @Test public void testGetSecuredOkapiClientFactory() throws IllegalAccessException { Vertx vertx = Vertx.vertx(); - OkapiClientFactory ocf = new OkapiClientFactory(vertx, OKAPI_URL, REQ_TIMEOUT_MS, KEYSTORE_TYPE, KEYSTORE_PROVIDER, - KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_ALIAS_PASSWORD); + TrustOptions trustOptions = new KeyStoreOptions() + .setType(KEYSTORE_TYPE) + .setProvider(KEYSTORE_PROVIDER) + .setPath(KEYSTORE_PATH) + .setPassword(KEYSTORE_PASSWORD) + .setAlias(KEY_ALIAS) + .setAliasPassword(KEY_ALIAS_PASSWORD); + OkapiClientFactory ocf = new OkapiClientFactory(vertx, OKAPI_URL, REQ_TIMEOUT_MS, trustOptions); String okapiUrl = (String) FieldUtils.readDeclaredField(ocf, "okapiURL"); Integer reqTimeoutMs = (Integer) FieldUtils.readDeclaredField(ocf, "reqTimeoutMs"); - KeyStoreOptions keyStoreOptions = (KeyStoreOptions) FieldUtils.readDeclaredField(ocf, "keyCertOptions", true); + KeyStoreOptions keyStoreOptions = (KeyStoreOptions) FieldUtils.readDeclaredField(ocf, "trustOptions", true); assertEquals(OKAPI_URL, okapiUrl); assertEquals(REQ_TIMEOUT_MS, reqTimeoutMs); diff --git a/src/test/java/org/folio/edge/core/utils/OkapiClientTest.java b/src/test/java/org/folio/edge/core/utils/OkapiClientTest.java index c4a79a8..81c2e77 100644 --- a/src/test/java/org/folio/edge/core/utils/OkapiClientTest.java +++ b/src/test/java/org/folio/edge/core/utils/OkapiClientTest.java @@ -4,7 +4,6 @@ import static org.folio.edge.core.Constants.HEADER_API_KEY; import static org.folio.edge.core.Constants.X_OKAPI_TENANT; import static org.folio.edge.core.Constants.X_OKAPI_TOKEN; -import static org.folio.edge.core.utils.test.MockOkapi.MOCK_TOKEN; import static org.folio.edge.core.utils.test.MockOkapi.X_DURATION; import static org.folio.edge.core.utils.test.MockOkapi.X_ECHO_STATUS; import static org.hamcrest.MatcherAssert.assertThat; @@ -15,6 +14,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; @@ -22,6 +22,9 @@ import java.util.List; import java.util.concurrent.TimeoutException; +import io.vertx.core.net.KeyStoreOptions; +import io.vertx.ext.web.client.WebClientOptions; +import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.edge.core.cache.TokenCacheFactory; @@ -453,4 +456,30 @@ public void testConstructorWithEmptySecondaryTenantHeaderVertx() { OkapiClient secondaryClient = new OkapiClient(Vertx.vertx(), "http://localhost:" + freePort, tenant, "", reqTimeout); assertEquals(secondaryClient.tenant, secondaryClient.defaultHeaders.get(X_OKAPI_TENANT)); } + + @Test + public void testConstructorForTlsWithNullTrustOptions() throws IllegalAccessException { + logger.info("=== Test tls constructor with null trustOptions ==="); + int freePort = TestUtils.getPort(); + OkapiClient tlsClient = new OkapiClient(Vertx.vertx(), "http://localhost:" + freePort, tenant, reqTimeout, null); + WebClientOptions options = (WebClientOptions) FieldUtils.readDeclaredField(tlsClient.client, "options", true); + + assertTrue(options.isSsl()); + assertNull(options.getTrustOptions()); + } + + @Test + public void testConstructorForTlsWithTrustOptionsPopulated() throws IllegalAccessException { + logger.info("=== Test tls constructor with trustOptions populated ==="); + int freePort = TestUtils.getPort(); + KeyStoreOptions trustOptions = new KeyStoreOptions() + .setType("JKS") + .setPath("some_path") + .setPassword("some_password"); + OkapiClient tlsClient = new OkapiClient(Vertx.vertx(), "http://localhost:" + freePort, tenant, reqTimeout, trustOptions); + WebClientOptions options = (WebClientOptions) FieldUtils.readDeclaredField(tlsClient.client, "options", true); + + assertTrue(options.isSsl()); + assertNotNull(options.getTrustOptions()); + } }