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

Hot Fix Release v4.6.1 #103

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from 10 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
15 changes: 14 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
## 4.6.3 2024-03-04

## 4.6.1 2024-05-23
[Full Changelog](https://github.com/folio-org/edge-common/compare/v4.6.0...v4.6.1)

## Stories
* [EDGCOMMON-79](https://folio-org.atlassian.net/browse/EDGCOMMON-79) - Add ssl support
SerhiiNosko marked this conversation as resolved.
Show resolved Hide resolved
* [EDGCOMMON-78](https://folio-org.atlassian.net/browse/EDGCOMMON-78) - Enhance HTTP Endpoint Security with TLS and FIPS-140-2 Compliant Cryptography


## 4.6.0 2024-03-11

* [EDGCOMMON-75](https://folio-org.atlassian.net/browse/EDGCOMMON-75) Quesnelia dep upgrades: Vert.x 4.5.4, aws 1.12.671, vault 6.2.0, …

## 4.5.3 2024-03-04

* [EDGCOMMON-74](https://folio-org.atlassian.net/browse/EDGCOMMON-74) aws-java-sdk-ssm 1.12.645 removing ion-java 1.0.2

Expand Down
56 changes: 43 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,22 +148,52 @@ 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

Property | Default | Description
------------------------ | ----------- | -------------
`port` | `8081` | Server port to listen on
`okapi_url` | *required* | Where to find Okapi (URL)
`secure_store` | `Ephemeral` | Type of secure store to use. Valid: `Ephemeral`, `AwsSsm`, `Vault`
`secure_store_props` | `NA` | Path to a properties file specifying secure store configuration
`token_cache_ttl_ms` | `3600000` | How long to cache JWTs, in milliseconds (ms)
`null_token_cache_ttl_ms`| `30000` | How long to cache login failure (null JWTs), in milliseconds (ms)
`token_cache_capacity` | `100` | Max token cache size
`log_level` | `INFO` | Log4j Log Level
`request_timeout_ms` | `30000` | Request Timeout
`api_key_sources` | `PARAM,HEADER,PATH` | Defines the sources (order of precendence) of the API key.
| Property | Default | Description |
|---------------------------|---------------------|---------------------------------------------------------------------------|
| `port` | `8081` | Server port to listen on |
| `okapi_url` | *required* | Where to find Okapi (URL) |
| `secure_store` | `Ephemeral` | Type of secure store to use. Valid: `Ephemeral`, `AwsSsm`, `Vault` |
| `secure_store_props` | `NA` | Path to a properties file specifying secure store configuration |
| `token_cache_ttl_ms` | `3600000` | How long to cache JWTs, in milliseconds (ms) |
| `null_token_cache_ttl_ms` | `30000` | How long to cache login failure (null JWTs), in milliseconds (ms) |
| `token_cache_capacity` | `100` | Max token cache size |
| `log_level` | `INFO` | Log4j Log Level |
| `request_timeout_ms` | `30000` | Request Timeout |
| `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

Expand Down
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<bc-fips.version>1.0.2.5</bc-fips.version>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -89,6 +90,13 @@
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<!-- Library provides the basic cryptographic functionality complying with FIPS for testing -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bc-fips</artifactId>
<version>${bc-fips.version}</version>
<scope>test</scope>
</dependency>

<!-- provided dependencies needed for MockOkapi and TestUtils -->
<dependency>
Expand Down
48 changes: 48 additions & 0 deletions src/main/java/org/folio/edge/core/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ private Constants() {
public static final String SYS_API_KEY_SOURCES = "api_key_sources";
public static final String SYS_RESPONSE_COMPRESSION = "response_compression";

// 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
SerhiiNosko marked this conversation as resolved.
Show resolved Hide resolved
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 All @@ -38,6 +55,7 @@ private Constants() {
public static final int DEFAULT_TOKEN_CACHE_CAPACITY = 100;
public static final String DEFAULT_API_KEY_SOURCES = "PARAM,HEADER,PATH";
public static final boolean DEFAULT_RESPONSE_COMPRESSION = false;
public static final boolean DEFAULT_SSL_ENABLED = false;

// Headers
public static final String X_OKAPI_TENANT = "x-okapi-tenant";
Expand Down Expand Up @@ -97,6 +115,36 @@ private Constants() {
defaultMap.put(SYS_RESPONSE_COMPRESSION,
Boolean.parseBoolean(System.getProperty(SYS_RESPONSE_COMPRESSION,
Boolean.toString(DEFAULT_RESPONSE_COMPRESSION))));
defaultMap.put(SYS_HTTP_SERVER_SSL_ENABLED,
Boolean.parseBoolean(System.getProperty(SYS_HTTP_SERVER_SSL_ENABLED,
Boolean.toString(DEFAULT_SSL_ENABLED))));
defaultMap.put(SYS_HTTP_SERVER_KEYSTORE_TYPE,
System.getProperty(SYS_HTTP_SERVER_KEYSTORE_TYPE));
defaultMap.put(SYS_HTTP_SERVER_KEYSTORE_PROVIDER,
System.getProperty(SYS_HTTP_SERVER_KEYSTORE_PROVIDER));
defaultMap.put(SYS_HTTP_SERVER_KEYSTORE_PATH,
System.getProperty(SYS_HTTP_SERVER_KEYSTORE_PATH));
defaultMap.put(SYS_HTTP_SERVER_KEYSTORE_PASSWORD,
System.getProperty(SYS_HTTP_SERVER_KEYSTORE_PASSWORD));
defaultMap.put(SYS_HTTP_SERVER_KEY_ALIAS,
System.getProperty(SYS_HTTP_SERVER_KEY_ALIAS));
defaultMap.put(SYS_HTTP_SERVER_KEY_ALIAS_PASSWORD,
System.getProperty(SYS_HTTP_SERVER_KEY_ALIAS_PASSWORD));
defaultMap.put(SYS_WEB_CLIENT_SSL_ENABLED,
Boolean.parseBoolean(System.getProperty(SYS_WEB_CLIENT_SSL_ENABLED,
Boolean.toString(DEFAULT_SSL_ENABLED))));
defaultMap.put(SYS_WEB_CLIENT_TRUSTSTORE_TYPE,
System.getProperty(SYS_WEB_CLIENT_TRUSTSTORE_TYPE));
defaultMap.put(SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER,
System.getProperty(SYS_WEB_CLIENT_TRUSTSTORE_PROVIDER));
defaultMap.put(SYS_WEB_CLIENT_TRUSTSTORE_PATH,
System.getProperty(SYS_WEB_CLIENT_TRUSTSTORE_PATH));
defaultMap.put(SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD,
System.getProperty(SYS_WEB_CLIENT_TRUSTSTORE_PASSWORD));
defaultMap.put(SYS_WEB_CLIENT_KEY_ALIAS,
System.getProperty(SYS_WEB_CLIENT_KEY_ALIAS));
defaultMap.put(SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD,
System.getProperty(SYS_WEB_CLIENT_KEY_ALIAS_PASSWORD));
defaultMap.put(SYS_SECURE_STORE_PROP_FILE,
System.getProperty(SYS_SECURE_STORE_PROP_FILE));
defaultMap.put(SYS_OKAPI_URL,
Expand Down
31 changes: 18 additions & 13 deletions src/main/java/org/folio/edge/core/EdgeVerticleHttp.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

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 @@ -25,23 +26,27 @@ public abstract class EdgeVerticleHttp extends EdgeVerticleCore {
@Override
public void start(Promise<Void> promise) {
Future.<Void>future(p -> super.start(p)).<Void>compose(res -> {
final int port = config().getInteger(SYS_PORT);
logger.info("Using port: {}", port);
final int port = config().getInteger(SYS_PORT);
logger.info("Using port: {}", port);

// initialize response compression
final boolean isCompressionSupported = config().getBoolean(SYS_RESPONSE_COMPRESSION);
logger.info("Response compression enabled: {}", isCompressionSupported);
final HttpServerOptions serverOptions = new HttpServerOptions();
serverOptions.setCompressionSupported(isCompressionSupported);
final HttpServerOptions serverOptions = new HttpServerOptions();

final HttpServer server = getVertx().createHttpServer(serverOptions);
// initialize response compression
final boolean isCompressionSupported = config().getBoolean(SYS_RESPONSE_COMPRESSION);
logger.info("Response compression enabled: {}", isCompressionSupported);
serverOptions.setCompressionSupported(isCompressionSupported);

final Router router = defineRoutes();
// initialize tls/ssl configuration for web server
SslConfigurationUtil.configureSslServerOptionsIfEnabled(config(), serverOptions);

return server.requestHandler(router)
.listen(port)
.mapEmpty();
}).onComplete(promise);
final HttpServer server = getVertx().createHttpServer(serverOptions);

final Router router = defineRoutes();

return server.requestHandler(router)
.listen(port)
.mapEmpty();
}).onComplete(promise);
}

public abstract Router defineRoutes();
Expand Down
29 changes: 26 additions & 3 deletions src/main/java/org/folio/edge/core/utils/OkapiClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.amazonaws.util.StringUtils;
import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
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 @@ -67,9 +68,25 @@ protected OkapiClient(Vertx vertx, String okapiURL, String tenant, int timeout)
this.reqTimeout = timeout;
this.okapiURL = okapiURL;
this.tenant = tenant;
WebClientOptions options = new WebClientOptions().setTryUseCompression(true)
.setIdleTimeoutUnit(TimeUnit.MILLISECONDS).setIdleTimeout(timeout)
.setConnectTimeout(timeout);
WebClientOptions options = initDefaultWebClientOptions(timeout);
client = WebClientFactory.getWebClient(vertx, options);
initDefaultHeaders();
}

/**
* 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);
if (trustOptions != null) {
options.setTrustOptions(trustOptions);
}
client = WebClientFactory.getWebClient(vertx, options);
initDefaultHeaders();
}
Expand All @@ -89,6 +106,12 @@ protected void initDefaultHeaders() {
defaultHeaders.add(X_OKAPI_TENANT, tenant);
}

protected WebClientOptions initDefaultWebClientOptions(int timeout) {
return new WebClientOptions().setTryUseCompression(true)
.setIdleTimeoutUnit(TimeUnit.MILLISECONDS).setIdleTimeout(timeout)
.setConnectTimeout(timeout);
}

public CompletableFuture<String> login(String username, String password) {
return doLogin(username, password, null).toCompletionStage().toCompletableFuture();
}
Expand Down
15 changes: 14 additions & 1 deletion src/main/java/org/folio/edge/core/utils/OkapiClientFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.concurrent.ConcurrentHashMap;

import io.vertx.core.Vertx;
import io.vertx.core.net.TrustOptions;

public class OkapiClientFactory {

Expand All @@ -12,14 +13,26 @@ public class OkapiClientFactory {
public final String okapiURL;
public final Vertx vertx;
public final int reqTimeoutMs;
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, TrustOptions trustOptions) {
this(vertx, okapiURL, reqTimeoutMs);
this.sslMode = true;
this.trustOptions = trustOptions;
}

public OkapiClient getOkapiClient(String tenant) {
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));
}
}
}
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