diff --git a/.trivyignore b/.trivyignore index be4a48a..1d9d2b1 100644 --- a/.trivyignore +++ b/.trivyignore @@ -5,3 +5,6 @@ # https://thetradedesk.atlassian.net/browse/UID2-4460 CVE-2024-47535 + +# https://thetradedesk.atlassian.net/browse/UID2-4874 +CVE-2025-24970 \ No newline at end of file diff --git a/src/main/java/com/uid2/core/vertx/CoreVerticle.java b/src/main/java/com/uid2/core/vertx/CoreVerticle.java index eb411fb..cfbe33b 100644 --- a/src/main/java/com/uid2/core/vertx/CoreVerticle.java +++ b/src/main/java/com/uid2/core/vertx/CoreVerticle.java @@ -22,7 +22,10 @@ import com.uid2.shared.vertx.RequestCapturingHandler; import com.uid2.shared.vertx.VertxUtils; import io.vertx.core.AbstractVerticle; +import io.vertx.core.Future; import io.vertx.core.Promise; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileProps; import io.vertx.core.file.FileSystem; import io.vertx.core.http.HttpHeaders; import io.vertx.core.http.HttpMethod; @@ -199,7 +202,7 @@ private Router createRoutesSetup() { router.get(Endpoints.OPERATORS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handleOperatorRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.PARTNERS_REFRESH.toString()).handler(auth.handle(attestationMiddleware.handle(this::handlePartnerRefresh), Role.OPTOUT_SERVICE)); router.get(Endpoints.OPS_HEALTHCHECK.toString()).handler(this::handleHealthCheck); - router.get(Endpoints.OPERATOR_CONFIG.toString()).handler(auth.handle(this::handleGetConfig, Role.OPERATOR)); + router.get(Endpoints.OPERATOR_CONFIG.toString()).handler(auth.handle(attestationMiddleware.handle(this::handleGetConfig), Role.OPERATOR)); if (Optional.ofNullable(ConfigStore.Global.getBoolean("enable_test_endpoints")).orElse(false)) { router.route(Endpoints.ATTEST_GET_TOKEN.toString()).handler(auth.handle(this::handleTestGetAttestationToken, Role.OPERATOR)); @@ -209,11 +212,22 @@ private Router createRoutesSetup() { } private void handleGetConfig(RoutingContext rc) { - fileSystem.readFile(com.uid2.core.Const.OPERATOR_CONFIG_PATH, ar -> { + String configPath = com.uid2.core.Const.OPERATOR_CONFIG_PATH; + + Future fileFuture = Future.future(promise -> + fileSystem.readFile(configPath, promise) + ); + Future propsFuture = Future.future(promise -> + fileSystem.props(configPath, promise) + ); + + Future.all(fileFuture, propsFuture).onComplete(ar -> { if (ar.succeeded()) { + Buffer fileBuffer = fileFuture.result(); + FileProps fileProps = propsFuture.result(); try { - String fileContent = ar.result().toString(); - JsonObject configJson = new JsonObject(fileContent); + JsonObject configJson = new JsonObject(fileBuffer.toString()); + configJson.put("version", fileProps.lastModifiedTime()); rc.response() .putHeader(HttpHeaders.CONTENT_TYPE, "application/json") .end(configJson.encodePrettily()); diff --git a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java index 1e5151e..cbc7d2c 100644 --- a/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java +++ b/src/test/java/com/uid2/core/vertx/TestCoreVerticle.java @@ -17,6 +17,7 @@ import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; +import io.vertx.core.file.FileProps; import io.vertx.core.file.FileSystem; import io.vertx.core.http.HttpHeaders; import io.vertx.core.json.JsonArray; @@ -79,6 +80,7 @@ public class TestCoreVerticle { private AttestationService attestationService; private String operatorConfig; + private final long operatorConfigVersion = 1; private static final String attestationProtocol = "test-attestation-protocol"; private static final String attestationProtocolPublic = "trusted"; @@ -135,6 +137,19 @@ void deployVerticle(TestInfo info, Vertx vertx, VertxTestContext testContext) th return null; }); + when(fileSystem.props(anyString(), any())).thenAnswer(invocation -> { + String path = invocation.getArgument(0); + Handler> handler = invocation.getArgument(1); + if (Objects.equals(path, com.uid2.core.Const.OPERATOR_CONFIG_PATH)) { + FileProps fileProps = mock(FileProps.class); + when(fileProps.lastModifiedTime()).thenReturn(operatorConfigVersion); + handler.handle(Future.succeededFuture(fileProps)); + } else { + handler.handle(Future.failedFuture(new RuntimeException("Failed to get file properties: " + path))); + } + return null; + }); + CoreVerticle verticle = new CoreVerticle(cloudStorage, authProvider, attestationService, attestationTokenService, enclaveIdentifierProvider, operatorJWTTokenProvider, jwtService, cloudEncryptionKeyProvider, fileSystem); vertx.deployVerticle(verticle, testContext.succeeding(id -> testContext.completeNow())); @@ -894,28 +909,41 @@ void keysRefreshSuccessNoHeaderVersion(Vertx vertx, VertxTestContext testContext }); } + @Tag("dontForceJwt") @Test void getConfigSuccess(Vertx vertx, VertxTestContext testContext) { - JsonObject expectedConfig = new JsonObject(operatorConfig); + JsonObject expectedConfig = new JsonObject(operatorConfig) + .put("version", operatorConfigVersion); - fakeAuth(Role.OPERATOR); + fakeAuth(attestationProtocolPublic, Role.OPERATOR); + addAttestationProvider(attestationProtocolPublic); + onHandleAttestationRequest(() -> { + byte[] resultPublicKey = null; + return Future.succeededFuture(new AttestationResult(resultPublicKey, "test")); + }); // Make HTTP Get request to operator config endpoint this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> { - assertEquals(200, response.statusCode()); - assertEquals("application/json", response.getHeader(HttpHeaders.CONTENT_TYPE)); - JsonObject actualConfig = new JsonObject(response.bodyAsString()); - assertEquals(expectedConfig, actualConfig); - testContext.completeNow(); - }) + assertEquals(200, response.statusCode()); + assertEquals("application/json", response.getHeader(HttpHeaders.CONTENT_TYPE)); + JsonObject actualConfig = new JsonObject(response.bodyAsString()); + assertEquals(expectedConfig, actualConfig); + testContext.completeNow(); + }) )); } + @Tag("dontForceJwt") @Test void getConfigInvalidJson(Vertx vertx, VertxTestContext testContext) { operatorConfig = "invalid config"; - fakeAuth(Role.OPERATOR); + fakeAuth(attestationProtocolPublic, Role.OPERATOR); + addAttestationProvider(attestationProtocolPublic); + onHandleAttestationRequest(() -> { + byte[] resultPublicKey = null; + return Future.succeededFuture(new AttestationResult(resultPublicKey, "test")); + }); this.get(vertx, Endpoints.OPERATOR_CONFIG.toString(), testContext.succeeding(response -> testContext.verify(() -> { assertEquals(500, response.statusCode());