Skip to content

Commit

Permalink
EDGCOMMON-79. Add truststore options to configure Web Clients
Browse files Browse the repository at this point in the history
  • Loading branch information
SerhiiNosko committed May 20, 2024
1 parent bd00a1e commit 2925d35
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 83 deletions.
37 changes: 30 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 HTTP server in 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, 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.
Expand Down
31 changes: 22 additions & 9 deletions src/main/java/org/folio/edge/core/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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));
Expand Down
28 changes: 14 additions & 14 deletions src/main/java/org/folio/edge/core/EdgeVerticleHttp.java
Original file line number Diff line number Diff line change
@@ -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_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_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_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.SYS_HTTP_SERVER_SSL_ENABLED;
import static org.folio.edge.core.Constants.TEXT_PLAIN;

import com.amazonaws.util.StringUtils;
Expand Down Expand Up @@ -67,27 +67,27 @@ protected void handleHealthCheck(RoutingContext ctx) {
}

private void configureSslIfEnabled(HttpServerOptions serverOptions) {
final boolean isSslEnabled = config().getBoolean(SYS_SSL_ENABLED);
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_KEYSTORE_TYPE);
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_KEYSTORE_PROVIDER);
String keystoreProvider = config().getString(SYS_HTTP_SERVER_KEYSTORE_PROVIDER);
logger.info("Using {} keystore provider for SSL/TLS", keystoreProvider);
String keystorePath = config().getString(SYS_KEYSTORE_PATH);
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_KEYSTORE_PASSWORD);
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_KEY_ALIAS);
String keyAliasPassword = config().getString(SYS_KEY_ALIAS_PASSWORD);
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)
Expand Down
14 changes: 10 additions & 4 deletions src/main/java/org/folio/edge/core/utils/OkapiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand Down
31 changes: 9 additions & 22 deletions src/main/java/org/folio/edge/core/utils/OkapiClientFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -14,38 +13,26 @@ 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;
this.okapiURL = okapiURL;
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));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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 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 (truststoreType != null && truststorePath != null && truststorePassword != null) {
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);
}
}
}
Loading

0 comments on commit 2925d35

Please sign in to comment.