-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Retrieve patron identifier (barcode) as part of checkin response * Add test * Explain GET /admin/health at port 8081 * Show port number 8081 in the log * SIP2-202: Vert.x 4.5.7 fixing netty form POST OOM CVE-2024-29025 https://folio-org.atlassian.net/browse/SIP2-202 Upgrade Vertx from 4.5.4 to 4.5.7. This indirectly upgrades Netty from 4.1.107.Final to 4.1.108.Final fixing netty-codec-http form POST OOM: GHSA-5jpm-x58v-624v https://www.cve.org/CVERecord?id=CVE-2024-29025 * [SIP2-200-1] Enable TLS on WebClient (#164) * [SIP2-200] Enable TLS on WebClient * [SIP2-203] - Update NEWS.md and revert the code. * [maven-release-plugin] prepare release v3.2.2 * [maven-release-plugin] prepare for next development iteration --------- Co-authored-by: Kurt Nordstrom <[email protected]> Co-authored-by: Julian Ladisch <[email protected]> Co-authored-by: BKadirkhodjaev <[email protected]>
- Loading branch information
1 parent
628a5b2
commit 9e7dd2a
Showing
8 changed files
with
252 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
src/main/java/org/folio/edge/sip2/utils/WebClientConfigException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.folio.edge.sip2.utils; | ||
|
||
public class WebClientConfigException extends RuntimeException { | ||
|
||
public WebClientConfigException(String message) { | ||
super(message); | ||
} | ||
} |
55 changes: 55 additions & 0 deletions
55
src/main/java/org/folio/edge/sip2/utils/WebClientUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package org.folio.edge.sip2.utils; | ||
|
||
import io.vertx.core.Vertx; | ||
import io.vertx.core.json.JsonArray; | ||
import io.vertx.core.json.JsonObject; | ||
import io.vertx.core.net.PemTrustOptions; | ||
import io.vertx.ext.web.client.WebClient; | ||
import io.vertx.ext.web.client.WebClientOptions; | ||
import java.util.Objects; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
|
||
|
||
public class WebClientUtils { | ||
|
||
public static final String SYS_PORT = "port"; | ||
public static final String SYS_NET_SERVER_OPTIONS = "netServerOptions"; | ||
public static final String SYS_PEM_KEY_CERT_OPTIONS = "pemKeyCertOptions"; | ||
public static final String SYS_CERT_PATHS = "certPaths"; | ||
private static final Logger log = LogManager.getLogger(); | ||
|
||
private WebClientUtils() { | ||
} | ||
|
||
/** | ||
* Create WebClient with TLS. | ||
* @param vertx instance | ||
* @param config json config | ||
* @return WebClient | ||
*/ | ||
public static WebClient create(Vertx vertx, JsonObject config) { | ||
JsonObject netServerOptions = config.getJsonObject(SYS_NET_SERVER_OPTIONS); | ||
if (Objects.nonNull(netServerOptions) | ||
&& netServerOptions.containsKey(SYS_PEM_KEY_CERT_OPTIONS)) { | ||
log.info("Creating WebClient with TLS on..."); | ||
|
||
JsonArray certPaths = netServerOptions.getJsonObject(SYS_PEM_KEY_CERT_OPTIONS) | ||
.getJsonArray(SYS_CERT_PATHS); | ||
if (Objects.isNull(certPaths) || certPaths.isEmpty()) { | ||
throw new WebClientConfigException("No TLS certPaths were found in config"); | ||
} | ||
|
||
final PemTrustOptions pemTrustOptions = new PemTrustOptions(); | ||
certPaths.forEach(entry -> pemTrustOptions.addCertPath((String) entry)); | ||
|
||
final WebClientOptions webClientOptions = new WebClientOptions() | ||
.setSsl(true) | ||
.setTrustOptions(pemTrustOptions); | ||
return WebClient.create(vertx, webClientOptions); | ||
} else { | ||
log.info("Creating WebClient with TLS off..."); | ||
return WebClient.create(vertx); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
166 changes: 166 additions & 0 deletions
166
src/test/java/org/folio/edge/sip2/utils/WebClientUtilsTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
package org.folio.edge.sip2.utils; | ||
|
||
import static org.folio.edge.sip2.utils.WebClientUtils.SYS_CERT_PATHS; | ||
import static org.folio.edge.sip2.utils.WebClientUtils.SYS_NET_SERVER_OPTIONS; | ||
import static org.folio.edge.sip2.utils.WebClientUtils.SYS_PEM_KEY_CERT_OPTIONS; | ||
import static org.folio.edge.sip2.utils.WebClientUtils.SYS_PORT; | ||
|
||
import io.netty.handler.codec.http.HttpResponseStatus; | ||
import io.vertx.core.Vertx; | ||
import io.vertx.core.file.FileSystem; | ||
import io.vertx.core.http.HttpHeaders; | ||
import io.vertx.core.http.HttpServer; | ||
import io.vertx.core.http.HttpServerOptions; | ||
import io.vertx.core.json.JsonArray; | ||
import io.vertx.core.json.JsonObject; | ||
import io.vertx.core.net.SelfSignedCertificate; | ||
import io.vertx.ext.web.client.WebClient; | ||
import io.vertx.junit5.VertxExtension; | ||
import io.vertx.junit5.VertxTestContext; | ||
import java.io.IOException; | ||
import java.net.ServerSocket; | ||
import org.apache.logging.log4j.LogManager; | ||
import org.apache.logging.log4j.Logger; | ||
import org.junit.jupiter.api.AfterEach; | ||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.api.extension.ExtendWith; | ||
|
||
@ExtendWith(VertxExtension.class) | ||
public class WebClientUtilsTests { | ||
|
||
private static final String RESPONSE_MESSAGE = "<OK>"; | ||
private static final Logger log = LogManager.getLogger(); | ||
private Integer serverPort; | ||
private SelfSignedCertificate selfSignedCertificate; | ||
|
||
@BeforeEach | ||
void setup() { | ||
this.serverPort = getAvailablePort(); | ||
this.selfSignedCertificate = SelfSignedCertificate.create(); | ||
} | ||
|
||
@AfterEach | ||
void tearDown() { | ||
this.serverPort = null; | ||
this.selfSignedCertificate = null; | ||
} | ||
|
||
@Test | ||
void testCreateWebClientTlsOff(Vertx vertx) { | ||
JsonObject config = new JsonObject(); | ||
Assertions.assertDoesNotThrow(() -> WebClientUtils.create(vertx, config)); | ||
} | ||
|
||
@Test | ||
void testCreateWebClientTlsOn(Vertx vertx) { | ||
JsonObject config = new JsonObject().put(SYS_NET_SERVER_OPTIONS, new JsonObject() | ||
.put(SYS_PEM_KEY_CERT_OPTIONS, selfSignedCertificate.keyCertOptions().toJson())); | ||
Assertions.assertDoesNotThrow(() -> WebClientUtils.create(vertx, config)); | ||
} | ||
|
||
@Test | ||
void testCreateWebClientTlsOnMultipleCerts(Vertx vertx) { | ||
JsonArray certPathsArray = new JsonArray() | ||
.add(SelfSignedCertificate.create().certificatePath()) | ||
.add(SelfSignedCertificate.create().certificatePath()) | ||
.add(SelfSignedCertificate.create().certificatePath()); | ||
JsonObject certPaths = new JsonObject().put(SYS_CERT_PATHS, certPathsArray); | ||
|
||
JsonObject config = new JsonObject().put(SYS_NET_SERVER_OPTIONS, new JsonObject() | ||
.put(SYS_PEM_KEY_CERT_OPTIONS, certPaths)); | ||
Assertions.assertDoesNotThrow(() -> WebClientUtils.create(vertx, config)); | ||
} | ||
|
||
@Test | ||
void testCreateWebClientWithMissingCertPaths(Vertx vertx) { | ||
JsonObject config = new JsonObject().put(SYS_NET_SERVER_OPTIONS, new JsonObject() | ||
.put(SYS_PEM_KEY_CERT_OPTIONS, new JsonObject())); | ||
Assertions.assertThrows(WebClientConfigException.class, | ||
() -> WebClientUtils.create(vertx, config)); | ||
} | ||
|
||
@Test | ||
void testCreateWebClientWithEmptyCertPaths(Vertx vertx) { | ||
JsonObject config = new JsonObject().put(SYS_NET_SERVER_OPTIONS, new JsonObject() | ||
.put(SYS_PEM_KEY_CERT_OPTIONS, new JsonObject().put(SYS_CERT_PATHS, new JsonArray()))); | ||
Assertions.assertThrows(WebClientConfigException.class, | ||
() -> WebClientUtils.create(vertx, config)); | ||
} | ||
|
||
@Test | ||
void testWebClientServerCommunication(Vertx vertx, VertxTestContext testContext) { | ||
JsonObject sipConfig = getCommonSipConfig(vertx); | ||
|
||
sipConfig.put(SYS_PORT, serverPort); | ||
sipConfig.put(SYS_NET_SERVER_OPTIONS, new JsonObject() | ||
.put(SYS_PEM_KEY_CERT_OPTIONS, selfSignedCertificate.keyCertOptions().toJson())); | ||
|
||
createServerTlsOn(vertx, testContext); | ||
|
||
final WebClient webClient = WebClientUtils.create(vertx, sipConfig); | ||
webClient.get(serverPort, "localhost", "/") | ||
.send() | ||
.onComplete(testContext.succeeding(response -> { | ||
String message = response.body().toString(); | ||
log.info("WebClient sent message to port {}, message: {}", serverPort, message); | ||
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode()); | ||
Assertions.assertEquals(RESPONSE_MESSAGE, message); | ||
testContext.completeNow(); | ||
})); | ||
} | ||
|
||
@Test | ||
void testFailingWebClientServerCommunication(Vertx vertx, VertxTestContext testContext) { | ||
JsonObject sipConfig = getCommonSipConfig(vertx); | ||
|
||
sipConfig.put(SYS_PORT, serverPort); | ||
sipConfig.put(SYS_NET_SERVER_OPTIONS, new JsonObject()); | ||
|
||
createServerTlsOn(vertx, testContext); | ||
|
||
final WebClient webClient = WebClientUtils.create(vertx, sipConfig); | ||
webClient.get(serverPort, "localhost", "/") | ||
.send() | ||
.onComplete(testContext.failing(err -> { | ||
log.info("Connection error: ", err); | ||
testContext.completeNow(); | ||
})); | ||
} | ||
|
||
private void createServerTlsOn(Vertx vertx, VertxTestContext testContext) { | ||
final HttpServerOptions httpServerOptions = new HttpServerOptions() | ||
.setPort(serverPort) | ||
.setSsl(true) | ||
.setKeyCertOptions(selfSignedCertificate.keyCertOptions()); | ||
|
||
final HttpServer httpServer = vertx.createHttpServer(httpServerOptions); | ||
httpServer | ||
.requestHandler(req -> req.response() | ||
.putHeader(HttpHeaders.CONTENT_TYPE, "text/plain") | ||
.end(RESPONSE_MESSAGE)) | ||
.listen(serverPort, http -> { | ||
if (http.succeeded()) { | ||
log.info("Server started on port: {}", serverPort); | ||
} else { | ||
testContext.failNow(http.cause()); | ||
} | ||
}); | ||
} | ||
|
||
private static JsonObject getCommonSipConfig(Vertx vertx) { | ||
final FileSystem fileSystem = vertx.fileSystem(); | ||
return fileSystem.readFileBlocking("test-sip2.conf").toJsonObject(); | ||
} | ||
|
||
private static int getAvailablePort() { | ||
do { | ||
try (ServerSocket socket = new ServerSocket(0)) { | ||
return socket.getLocalPort(); | ||
} catch (IOException e) { | ||
// ignore | ||
} | ||
} while (true); | ||
} | ||
} |