From 09f6f1c1f498921e08ceb227c79cb970527f843c Mon Sep 17 00:00:00 2001 From: Serhii Nosko Date: Mon, 27 May 2024 10:42:22 +0300 Subject: [PATCH] =?UTF-8?q?EDGPATRON-130.=20Enhance=20HTTP=20Endpoint=20Se?= =?UTF-8?q?curity=20with=20TLS=20and=20FIPS-140-2=E2=80=A6=20(#111)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * EDGPATRON-130. Enhance HTTP Endpoint Security with TLS and FIPS-140-2 Compliant Cryptography --- Jenkinsfile | 3 +- README.md | 65 +++++++++++++------ pom.xml | 4 +- .../org/folio/edge/patron/MainVerticle.java | 6 +- .../org/folio/edge/patron/PatronHandler.java | 4 +- .../edge/patron/utils/PatronOkapiClient.java | 5 -- .../utils/PatronOkapiClientFactory.java | 16 ----- .../utils/PatronOkapiClientFactoryTest.java | 30 --------- .../patron/utils/PatronOkapiClientTest.java | 5 +- 9 files changed, 57 insertions(+), 81 deletions(-) delete mode 100644 src/main/java/org/folio/edge/patron/utils/PatronOkapiClientFactory.java delete mode 100644 src/test/java/org/folio/edge/patron/utils/PatronOkapiClientFactoryTest.java diff --git a/Jenkinsfile b/Jenkinsfile index 1f8ec9a..ec0d242 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -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 MainVerticleTest.java } } } diff --git a/README.md b/README.md index cd4a792..1762413 100644 --- a/README.md +++ b/README.md @@ -28,34 +28,61 @@ Institutional users should be granted the following permissions in order to use 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 failures (null JWTs), in milliseconds (ms) -`token_cache_capacity` | `100` | Max token cache size -`patron_id_cache_ttl_ms` | `3600000` | How long to cache patron ID mappings in milliseconds (ms) -`null_patron_id_cache_ttl_ms` | `30000` | How long to cache patron lookup failures in milliseconds (ms) -`patron_id_cache_capacity` | `1000` | Max token cache size -`log_level` | `INFO` | Log4j Log Level -`request_timeout_ms` | `30000` | Request Timeout +| 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 failures (null JWTs), in milliseconds (ms) | +| `token_cache_capacity` | `100` | Max token cache size | +| `patron_id_cache_ttl_ms` | `3600000` | How long to cache patron ID mappings in milliseconds (ms) | +| `null_patron_id_cache_ttl_ms` | `30000` | How long to cache patron lookup failures in milliseconds (ms) | +| `patron_id_cache_capacity` | `1000` | Max token cache size | +| `log_level` | `INFO` | Log4j Log Level | +| `request_timeout_ms` | `30000` | Request Timeout | + +### Env variables 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 TLS for the server should be enabled. + +| Property | Default | Description | +|-----------------------------------------------------|------------------|---------------------------------------------------------------------------------------------| +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEYSTORE_TYPE` | `NA` | (Required). Set the type of the keystore. Common types include `JKS`, `PKCS12`, and `BCFKS` | +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEYSTORE_LOCATION` | `NA` | (Required). Set the location of the keystore file in the local file system | +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEYSTORE_PASSWORD` | `NA` | (Required). Set the password for the keystore | +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEY_ALIAS` | `NA` | Set the alias of the key within the keystore. | +| `SPRING_SSL_BUNDLE_JKS_WEBSERVER_KEY_PASSWORD` | `NA` | Optional param that points to a password of `KEY_ALIAS` if it protected | + +### Env variables 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 `FOLIO_CLIENT_TLS_ENABLED = true`. +If truststore parameters need to be populated, `FOLIO_CLIENT_TLS_TRUSTSTORETYPE`, `FOLIO_CLIENT_TLS_TRUSTSTOREPATH` and `FOLIO_CLIENT_TLS_TRUSTSTOREPASSWORD` are required. + +| Property | Default | Description | +|-----------------------------------------|-------------------|----------------------------------------------------------------------------------| +| `FOLIO_CLIENT_TLS_ENABLED` | `false` | Set whether SSL/TLS is enabled for Vertx Http Server | +| `FOLIO_CLIENT_TLS_TRUSTSTORETYPE` | `NA` | Set the type of the keystore. Common types include `JKS`, `PKCS12`, and `BCFKS` | +| `FOLIO_CLIENT_TLS_TRUSTSTOREPATH` | `NA` | Set the location of the keystore file in the local file system | +| `FOLIO_CLIENT_TLS_TRUSTSTOREPASSWORD` | `NA` | Set the password for the keystore | + ## Patron Mapping In order to map external patron IDs to those used within FOLIO, the `externalSystemId` field in the user metadata is used. The mapping flow works like this: 1. A request arrives containing an external system's patron ID -1. The patron ID cache is consulted. If a mapping has been cached, skip to #5. -1. A request is made to mod-users, querying for the user having the provided `externalSystemId` -1. The external ID -> internal/FOLIO ID mapping is cached for a configurable amount of time. -1. The internal/FOLIO ID is used when calling mod-patron +2. The patron ID cache is consulted. If a mapping has been cached, skip to #5. +3. A request is made to mod-users, querying for the user having the provided `externalSystemId` +4. The external ID -> internal/FOLIO ID mapping is cached for a configurable amount of time. +5. The internal/FOLIO ID is used when calling mod-patron ## Additional information diff --git a/pom.xml b/pom.xml index 3f08745..d39debe 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ org.folio.edge.patron.MainVerticle - 4.5.5 + 4.5.7 @@ -100,7 +100,7 @@ org.folio edge-common - 4.5.1 + 4.7.0 diff --git a/src/main/java/org/folio/edge/patron/MainVerticle.java b/src/main/java/org/folio/edge/patron/MainVerticle.java index 9057a90..a4a8a85 100644 --- a/src/main/java/org/folio/edge/patron/MainVerticle.java +++ b/src/main/java/org/folio/edge/patron/MainVerticle.java @@ -10,8 +10,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.folio.edge.core.EdgeVerticleHttp; +import org.folio.edge.core.utils.OkapiClientFactory; +import org.folio.edge.core.utils.OkapiClientFactoryInitializer; import org.folio.edge.patron.cache.PatronIdCache; -import org.folio.edge.patron.utils.PatronOkapiClientFactory; import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; @@ -44,8 +45,7 @@ public MainVerticle() { @Override public Router defineRoutes() { - PatronOkapiClientFactory ocf = new PatronOkapiClientFactory(vertx, config().getString(org.folio.edge.core.Constants.SYS_OKAPI_URL), - config().getInteger(org.folio.edge.core.Constants.SYS_REQUEST_TIMEOUT_MS)); + OkapiClientFactory ocf = OkapiClientFactoryInitializer.createInstance(vertx, config()); PatronHandler patronHandler = new PatronHandler(secureStore, ocf); Router router = Router.router(vertx); diff --git a/src/main/java/org/folio/edge/patron/PatronHandler.java b/src/main/java/org/folio/edge/patron/PatronHandler.java index af3e4df..019a709 100644 --- a/src/main/java/org/folio/edge/patron/PatronHandler.java +++ b/src/main/java/org/folio/edge/patron/PatronHandler.java @@ -38,12 +38,12 @@ import org.folio.edge.core.Handler; import org.folio.edge.core.security.SecureStore; import org.folio.edge.core.utils.OkapiClient; +import org.folio.edge.core.utils.OkapiClientFactory; import org.folio.edge.patron.model.error.Error; import org.folio.edge.patron.model.error.ErrorMessage; import org.folio.edge.patron.model.error.Errors; import org.folio.edge.patron.utils.PatronIdHelper; import org.folio.edge.patron.utils.PatronOkapiClient; -import org.folio.edge.patron.utils.PatronOkapiClientFactory; public class PatronHandler extends Handler { @@ -51,7 +51,7 @@ public class PatronHandler extends Handler { + " parameter value {%s} is not valid: must be an integer, greater than or equal to 0"; private static final Logger logger = LogManager.getLogger(Handler.class); - public PatronHandler(SecureStore secureStore, PatronOkapiClientFactory ocf) { + public PatronHandler(SecureStore secureStore, OkapiClientFactory ocf) { super(secureStore, ocf); } diff --git a/src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java b/src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java index 5840f6a..5f24210 100644 --- a/src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java +++ b/src/main/java/org/folio/edge/patron/utils/PatronOkapiClient.java @@ -8,7 +8,6 @@ import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Promise; -import io.vertx.core.Vertx; import io.vertx.core.json.JsonObject; import org.folio.edge.patron.model.Hold; import org.joda.time.DateTime; @@ -26,10 +25,6 @@ public PatronOkapiClient(OkapiClient client, String alternateTenantId) { super(client, alternateTenantId); } - protected PatronOkapiClient(Vertx vertx, String okapiURL, String tenantId, String alternateTenantId, int timeout) { - super(vertx, okapiURL, tenantId, alternateTenantId, timeout); - } - private void getPatron(String extPatronId, Handler> responseHandler, Handler exceptionHandler) { get( diff --git a/src/main/java/org/folio/edge/patron/utils/PatronOkapiClientFactory.java b/src/main/java/org/folio/edge/patron/utils/PatronOkapiClientFactory.java deleted file mode 100644 index f565e32..0000000 --- a/src/main/java/org/folio/edge/patron/utils/PatronOkapiClientFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.folio.edge.patron.utils; - -import org.folio.edge.core.utils.OkapiClientFactory; - -import io.vertx.core.Vertx; - -public class PatronOkapiClientFactory extends OkapiClientFactory { - - public PatronOkapiClientFactory(Vertx vertx, String okapiURL, int reqTimeoutMs) { - super(vertx, okapiURL, reqTimeoutMs); - } - - public PatronOkapiClient getPatronOkapiClient(String tenant) { - return new PatronOkapiClient(vertx, okapiURL, tenant, "", reqTimeoutMs); - } -} diff --git a/src/test/java/org/folio/edge/patron/utils/PatronOkapiClientFactoryTest.java b/src/test/java/org/folio/edge/patron/utils/PatronOkapiClientFactoryTest.java deleted file mode 100644 index 3159dc8..0000000 --- a/src/test/java/org/folio/edge/patron/utils/PatronOkapiClientFactoryTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.folio.edge.patron.utils; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -import org.junit.Before; -import org.junit.Test; - -import io.vertx.core.Vertx; - -public class PatronOkapiClientFactoryTest { - - private static final int reqTimeout = 5000; - - private PatronOkapiClientFactory ocf; - - @Before - public void setUp() throws Exception { - - Vertx vertx = Vertx.vertx(); - ocf = new PatronOkapiClientFactory(vertx, "http://mocked.okapi:9130", reqTimeout); - } - - @Test - public void testGetOkapiClient() { - PatronOkapiClient client = ocf.getPatronOkapiClient("tenant"); - assertNotNull(client); - assertEquals(reqTimeout, client.reqTimeout); - } -} diff --git a/src/test/java/org/folio/edge/patron/utils/PatronOkapiClientTest.java b/src/test/java/org/folio/edge/patron/utils/PatronOkapiClientTest.java index 9f921a9..d3af8fb 100644 --- a/src/test/java/org/folio/edge/patron/utils/PatronOkapiClientTest.java +++ b/src/test/java/org/folio/edge/patron/utils/PatronOkapiClientTest.java @@ -15,6 +15,7 @@ import io.vertx.core.json.JsonObject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.folio.edge.core.utils.OkapiClientFactory; import org.folio.edge.core.utils.test.TestUtils; import org.folio.edge.patron.model.Account; import org.folio.edge.patron.model.Hold; @@ -53,8 +54,8 @@ public void setUp(TestContext context) throws Exception { mockOkapi.start() .onComplete(context.asyncAssertSuccess()); - client = new PatronOkapiClientFactory(Vertx.vertx(), "http://localhost:" + okapiPort, reqTimeout) - .getPatronOkapiClient(tenant); + client = new PatronOkapiClient(new OkapiClientFactory(Vertx.vertx(), + "http://localhost:" + okapiPort, reqTimeout).getOkapiClient(tenant), "alternateTenantId"); } @After