Skip to content

Commit

Permalink
[EDGOAIPMH-116/EDGOAIPMH-117] - Enhance WebClient TLS Configuration f…
Browse files Browse the repository at this point in the history
…or Secure Connections to OKAPI (#115)

* [SIP2-200/SIP2-201] - Enhance WebClient TLS Configuration for Secure Connections to OKAPI
  • Loading branch information
azizbekxm authored May 23, 2024
1 parent c6d508a commit 067c10e
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 98 deletions.
3 changes: 1 addition & 2 deletions Jenkinsfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ buildMvn {
doDocker = {
buildJavaDocker {
publishMaster = 'yes'
healthChk = 'yes'
healthChkCmd = 'wget --no-verbose --tries=1 --spider http://localhost:8081/admin/health || exit 1'
//healthChk for /admin/health in OaiPmhTest.java
}
}
}
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,48 @@ For stable operation, the application requires the following memory configuratio

For example, to enable HTTP compression based on `Accept-Encoding` header the `-Dresponse_compression=true` should be specified as VM option.

### System Properties

| Property | Default | Description |
|------------------------|-------------------|-------------------------------------------------------------------------|
| `port` | `8081` | Server port to listen on |
| `okapi_url` | *required* | Where to find Okapi (URL) |
| `request_timeout_ms` | `30000` | Request Timeout |
| `log_level` | `INFO` | Log4j Log Level |
| `token_cache_capacity` | `100` | Max token cache size |
| `token_cache_ttl_ms` | `100` | How long to cache JWTs, in milliseconds (ms) |
| `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 |

### 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` | Point to a password of `key_alias` if it is 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` | Point to a password of `key_alias` if it is protected |

### Issue tracker
See project [EDGOAIPMH](https://issues.folio.org/browse/EDGOAIPMH)
at the [FOLIO issue tracker](https://dev.folio.org/guidelines/issue-tracker).
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
<jaxb-impl.version>2.3.6</jaxb-impl.version>
<jaxb-core.version>4.0.0</jaxb-core.version>
<mod-configuration-client.version>5.9.2</mod-configuration-client.version>
<edge.common.version>4.6.0</edge.common.version>
<edge.common.version>4.7.0-SNAPSHOT</edge.common.version>
<vertx-stack-depchain.version>4.5.4</vertx-stack-depchain.version>
<log4j-bom.version>2.23.0</log4j-bom.version>
<mockito-core.version>4.6.1</mockito-core.version>
Expand Down
10 changes: 4 additions & 6 deletions src/main/java/org/folio/edge/oaipmh/MainVerticle.java
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
package org.folio.edge.oaipmh;

import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES;
import static org.folio.edge.core.Constants.SYS_OKAPI_URL;
import static org.folio.edge.core.Constants.SYS_REQUEST_TIMEOUT_MS;

import io.vertx.core.json.jackson.DatabindCodec;
import lombok.extern.slf4j.Slf4j;
import org.folio.edge.core.Constants;
import org.folio.edge.core.EdgeVerticleHttp;
import org.folio.edge.oaipmh.clients.OaiPmhOkapiClientFactory;
import org.folio.edge.core.utils.OkapiClientFactory;

import io.vertx.core.http.HttpMethod;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import org.folio.edge.core.utils.OkapiClientFactoryInitializer;

@Slf4j
public class MainVerticle extends EdgeVerticleHttp {

@Override
public Router defineRoutes() {
String okapiURL = config().getString(SYS_OKAPI_URL);
int reqTimeoutMs = config().getInteger(SYS_REQUEST_TIMEOUT_MS);
// first call to mod-oai-pmh is supposed to take significant time
// if the timeout is not set via env vars, it will be 2 hours
if (reqTimeoutMs == Constants.DEFAULT_REQUEST_TIMEOUT_MS) {
reqTimeoutMs = 7200000;
config().put(SYS_REQUEST_TIMEOUT_MS, 7200000);
}

DatabindCodec.mapper().configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

OaiPmhOkapiClientFactory ocf = new OaiPmhOkapiClientFactory(vertx, okapiURL, reqTimeoutMs);
OkapiClientFactory ocf = OkapiClientFactoryInitializer.createInstance(vertx, config());
OaiPmhHandler oaiPmhHandler = new OaiPmhHandler(secureStore, ocf);

Router router = Router.router(vertx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import lombok.extern.slf4j.Slf4j;
import org.folio.edge.core.utils.OkapiClient;
import org.folio.rest.jaxrs.model.ConsortiumCollection;
Expand All @@ -19,8 +18,8 @@ public class ConsortiaClient extends OkapiClient {
private static final String CONSORTIA_ENDPOINT = "/consortia";
private static final String CONSORTIA_TENANTS_ENDPOINT_TEMPLATE = "/consortia/%s/tenants?limit=";

ConsortiaClient(Vertx vertx, String okapiURL, String tenant, int timeout) {
super(vertx, okapiURL, tenant, timeout);
public ConsortiaClient(OkapiClient client) {
super(client);
}

public Future<List<String>> getTenantList(String initialTenant, MultiMap headers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import lombok.extern.slf4j.Slf4j;
import org.folio.edge.core.utils.OkapiClient;
import org.folio.rest.jaxrs.model.UserTenantCollection;
Expand All @@ -16,13 +15,11 @@
@Slf4j
public class ConsortiaTenantClient extends OkapiClient {
private static final String USER_TENANTS_ENDPOINT_LIMIT_1 = "/user-tenants?limit=1";
private final OkapiClient okapiClient;

public ConsortiaTenantClient(OkapiClient client) {
super(client);
}

ConsortiaTenantClient(Vertx vertx, String okapiURL, String tenant, int timeout) {
super(vertx, okapiURL, tenant, timeout);
okapiClient = client;
}

public Future<List<String>> getConsortiaTenants(MultiMap headers) {
Expand All @@ -36,7 +33,7 @@ private Future<List<String>> processUserTenants(UserTenantCollection userTenantC
if (isNotEmpty(userTenants)) {
var centralTenantId = userTenants.get(0).getCentralTenantId();
if (Objects.equals(tenant, centralTenantId)) {
var consortiaClient = new ConsortiaClient(vertx, okapiURL, centralTenantId, reqTimeout);
var consortiaClient = new ConsortiaClient(okapiClient);
consortiaClient.setToken(getToken());
return consortiaClient.getTenantList(tenant, headers);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import lombok.extern.slf4j.Slf4j;

@Slf4j
Expand All @@ -24,11 +23,6 @@ public OaiPmhOkapiClient(OkapiClient client) {
fixDefaultHeaders();
}

OaiPmhOkapiClient(Vertx vertx, String okapiURL, String tenant, int timeout) {
super(vertx, okapiURL, tenant, timeout);
fixDefaultHeaders();
}

// EDGOAIPMH-39 - the defaultHeaders map from OkapiClient (edge-common) contains
// Accept: application/json, text/plain
// so we need to replace it to "application/xml, text/xml"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package org.folio.edge.oaipmh.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_SSL_ENABLED;

import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonObject;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;
import lombok.extern.slf4j.Slf4j;
import org.folio.edge.core.utils.OkapiClientFactory;
import org.folio.edge.core.utils.OkapiClientFactoryInitializer;
import org.folio.edge.core.utils.test.TestUtils;
import org.folio.edge.oaipmh.clients.OaiPmhOkapiClientFactory;
import org.folio.edge.oaipmh.clients.ConsortiaTenantClient;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -29,7 +36,7 @@ class ConsortiaTenantClientTest {
private static final String TENANT_EMPTY_CONSORTIA = "empty_consortia";
private static final int REQUEST_TIMEOUT = 3000;

private OaiPmhOkapiClientFactory factory;
private OkapiClientFactory factory;
private OaiPmhMockOkapi mockOkapi;

@BeforeEach
Expand All @@ -42,7 +49,7 @@ void setUp(Vertx vertx, VertxTestContext context) {
knownTenants.add(TENANT_CONSORTIA);
knownTenants.add(TENANT_EMPTY_CONSORTIA);

factory = new OaiPmhOkapiClientFactory(vertx, "http://localhost:" + okapiPort, REQUEST_TIMEOUT);
factory = OkapiClientFactoryInitializer.createInstance(vertx, getCommonConfig(okapiPort));

mockOkapi = new OaiPmhMockOkapi(vertx, okapiPort, knownTenants);
mockOkapi.start(context);
Expand Down Expand Up @@ -87,7 +94,7 @@ void shouldReturnTenantListWithoutCentralIfTenantIsCentral(VertxTestContext cont
}

private void processRequest(VertxTestContext context, String tenant, MultiMap headers, List<String> expectedList) {
var client = factory.getConsortiaTenantClient(tenant);
var client = new ConsortiaTenantClient(factory.getOkapiClient(tenant));
client.login("admin", "password")
.thenCompose(v -> client.getConsortiaTenants(headers).toCompletionStage())
.thenAccept(list -> {
Expand All @@ -98,4 +105,11 @@ private void processRequest(VertxTestContext context, String tenant, MultiMap he
}
});
}

private JsonObject getCommonConfig(int okapiPort) {
return new JsonObject()
.put(SYS_OKAPI_URL, "http://localhost:" + okapiPort)
.put(SYS_REQUEST_TIMEOUT_MS, REQUEST_TIMEOUT)
.put(SYS_WEB_CLIENT_SSL_ENABLED, false);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
package org.folio.edge.oaipmh.utils;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpStatus;
import org.folio.edge.core.utils.OkapiClientFactory;
import org.folio.edge.core.utils.test.TestUtils;
import org.folio.edge.oaipmh.clients.OaiPmhOkapiClient;
import org.folio.edge.oaipmh.clients.OaiPmhOkapiClientFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.openarchives.oai._2.VerbType;

import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.junit5.VertxExtension;
import io.vertx.junit5.VertxTestContext;
import lombok.extern.slf4j.Slf4j;

import static org.junit.jupiter.api.Assertions.assertEquals;

@Slf4j
@ExtendWith(VertxExtension.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
Expand All @@ -40,9 +40,8 @@ void setUp(Vertx vertx, VertxTestContext context) {
List<String> knownTenants = new ArrayList<>();
knownTenants.add(TENANT);

client = new OaiPmhOkapiClientFactory(vertx,
"http://localhost:" + okapiPort, REQUEST_TIMEOUT)
.getOaiPmhOkapiClient(TENANT);
client = new OaiPmhOkapiClient(new OkapiClientFactory(Vertx.vertx(),
"http://localhost:" + okapiPort, REQUEST_TIMEOUT).getOkapiClient(TENANT));

mockOkapi = new OaiPmhMockOkapi(vertx, okapiPort, knownTenants);
mockOkapi.start(context);
Expand Down

0 comments on commit 067c10e

Please sign in to comment.