Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EDGCOMMON-79. Add truststore options to configure Web Clients #101

Merged
merged 6 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
steveellis marked this conversation as resolved.
Show resolved Hide resolved
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.
steveellis marked this conversation as resolved.
Show resolved Hide resolved

| 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
45 changes: 2 additions & 43 deletions src/main/java/org/folio/edge/core/EdgeVerticleHttp.java
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -45,7 +37,7 @@ public void start(Promise<Void> promise) {
serverOptions.setCompressionSupported(isCompressionSupported);

// initialize tls/ssl configuration for web server
configureSslIfEnabled(serverOptions);
SslConfigurationUtil.configureSslServerOptionsIfEnabled(config(), serverOptions);

final HttpServer server = getVertx().createHttpServer(serverOptions);

Expand All @@ -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));
}
}
}
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,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);
}
}
}
Loading