diff --git a/.trivyignore b/.trivyignore index 55309276c..1aa30ac1d 100644 --- a/.trivyignore +++ b/.trivyignore @@ -21,4 +21,8 @@ CVE-2025-65018 exp:2026-06-05 CVE-2025-66293 exp:2026-06-15 # UID2-6481 -CVE-2025-68973 exp:2026-06-15 \ No newline at end of file +CVE-2025-68973 exp:2026-06-15 + +# Libpng for testing +CVE-2026-22695 +CVE-2026-22801 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7f9f95f75..46cce2247 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,8 @@ RUN adduser -D uid2-operator && mkdir -p /opt/uid2 && chmod 777 -R /opt/uid2 && USER uid2-operator CMD java \ - -XX:MaxRAMPercentage=95 -XX:-UseCompressedOops -XX:+PrintFlagsFinal -XX:-OmitStackTraceInFastThrow \ + -XX:+UseZGC -XX:+ZGenerational \ + -XX:MaxRAMPercentage=85 -XX:-UseCompressedOops -XX:+PrintFlagsFinal -XX:-OmitStackTraceInFastThrow \ -Djava.security.egd=file:/dev/./urandom \ -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory \ -Dlogback.configurationFile=/app/conf/logback.xml \ diff --git a/conf/default-config.json b/conf/default-config.json index c5de0d87b..3b3c1c78e 100644 --- a/conf/default-config.json +++ b/conf/default-config.json @@ -40,5 +40,7 @@ "sharing_token_expiry_seconds": 2592000, "operator_type": "public", "enable_remote_config": true, - "uid_instance_id_prefix": "local-operator" + "uid_instance_id_prefix": "local-operator", + "enable_async_batch_request": true, + "compute_pool_thread_count": 12 } diff --git a/pom.xml b/pom.xml index 45d68d711..88ed2aa1a 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-operator - 5.63.30 + 5.63.35-alpha-294-SNAPSHOT UTF-8 diff --git a/src/main/java/com/uid2/operator/Const.java b/src/main/java/com/uid2/operator/Const.java index 14f878371..406c52df2 100644 --- a/src/main/java/com/uid2/operator/Const.java +++ b/src/main/java/com/uid2/operator/Const.java @@ -39,5 +39,8 @@ public class Config extends com.uid2.shared.Const.Config { public static final String RuntimeConfigMetadataPathProp = "runtime_config_metadata_path"; public static final String IdentityEnvironmentProp = "identity_environment"; + + public static final String ComputePoolThreadCountProp = "compute_pool_thread_count"; + public static final String EnableAsyncBatchRequestProp = "enable_async_batch_request"; } } diff --git a/src/main/java/com/uid2/operator/Main.java b/src/main/java/com/uid2/operator/Main.java index b4e79f5c2..ca62ea4c6 100644 --- a/src/main/java/com/uid2/operator/Main.java +++ b/src/main/java/com/uid2/operator/Main.java @@ -341,8 +341,13 @@ private void run() throws Exception { this.createVertxInstancesMetric(); this.createVertxEventLoopsMetric(); + // Create shared compute pool for CPU-intensive operations + final int computePoolSize = config.getInteger(Const.Config.ComputePoolThreadCountProp, Math.max(1, Runtime.getRuntime().availableProcessors() - 2)); + final WorkerExecutor computeWorkerPool = vertx.createSharedWorkerExecutor("compute", computePoolSize); + LOGGER.info("Created compute worker pool with size: {}", computePoolSize); + Supplier operatorVerticleSupplier = () -> { - UIDOperatorVerticle verticle = new UIDOperatorVerticle(configStore, config, this.clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, getKeyManager(), saltProvider, optOutStore, Clock.systemUTC(), _statsCollectorQueue, new SecureLinkValidatorService(this.serviceLinkProvider, this.serviceProvider), this.shutdownHandler::handleSaltRetrievalResponse, this.uidInstanceIdProvider); + UIDOperatorVerticle verticle = new UIDOperatorVerticle(configStore, config, this.clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, getKeyManager(), saltProvider, optOutStore, Clock.systemUTC(), _statsCollectorQueue, new SecureLinkValidatorService(this.serviceLinkProvider, this.serviceProvider), this.shutdownHandler::handleSaltRetrievalResponse, this.uidInstanceIdProvider, computeWorkerPool); return verticle; }; @@ -371,6 +376,9 @@ private void run() throws Exception { }) .onFailure(t -> { LOGGER.error("Failed to bootstrap operator: " + t.getMessage(), new Exception(t)); + if (computeWorkerPool != null) { + computeWorkerPool.close(); + } vertx.close(); System.exit(1); }); @@ -488,7 +496,7 @@ private static Vertx createVertx() { MicrometerMetricsOptions metricOptions = new MicrometerMetricsOptions() .setPrometheusOptions(prometheusOptions) - .setLabels(EnumSet.of(Label.HTTP_METHOD, Label.HTTP_CODE, Label.HTTP_PATH)) + .setLabels(EnumSet.of(Label.HTTP_METHOD, Label.HTTP_CODE, Label.HTTP_PATH, Label.POOL_NAME)) .setJvmMetricsEnabled(true) .setEnabled(true); setupMetrics(metricOptions); @@ -499,7 +507,8 @@ private static Vertx createVertx() { VertxOptions vertxOptions = new VertxOptions() .setMetricsOptions(metricOptions) - .setBlockedThreadCheckInterval(threadBlockedCheckInterval); + .setBlockedThreadCheckInterval(threadBlockedCheckInterval) + .setWorkerPoolSize(12); return Vertx.vertx(vertxOptions); } @@ -524,6 +533,7 @@ private static void setupMetrics(MicrometerMetricsOptions metricOptions) { Objects.equals(id.getTag(Label.HTTP_CODE.toString()), "404"))) .meterFilter(new MeterFilter() { private final String httpServerResponseTime = MetricsDomain.HTTP_SERVER.getPrefix() + MetricsNaming.v4Names().getHttpResponseTime(); + private final String poolQueueTime = MetricsDomain.NAMED_POOLS.getPrefix() + MetricsNaming.v4Names().getPoolQueueTime(); @Override public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) { @@ -533,6 +543,12 @@ public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticC .build() .merge(config); } + if (id.getName().equals(poolQueueTime)) { + return DistributionStatisticConfig.builder() + .percentiles(0.50, 0.90, 0.95, 0.99) + .build() + .merge(config); + } return config; } }) diff --git a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java index 7b36e8eea..075a6894a 100644 --- a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java +++ b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java @@ -43,6 +43,7 @@ import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Promise; +import io.vertx.core.WorkerExecutor; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerResponse; @@ -133,6 +134,7 @@ public class UIDOperatorVerticle extends AbstractVerticle { public static final long OPT_OUT_CHECK_CUTOFF_DATE = Instant.parse("2023-09-01T00:00:00.00Z").getEpochSecond(); private final Handler saltRetrievalResponseHandler; private final int allowClockSkewSeconds; + private final WorkerExecutor computeWorkerPool; protected Map> siteIdToInvalidOriginsAndAppNames = new HashMap<>(); protected boolean keySharingEndpointProvideAppNames; protected Instant lastInvalidOriginProcessTime = Instant.now(); @@ -140,6 +142,8 @@ public class UIDOperatorVerticle extends AbstractVerticle { private final int optOutStatusMaxRequestSize; private final boolean optOutStatusApiEnabled; + private final boolean isAsyncBatchRequestsEnabled; + //"Android" is from https://github.com/IABTechLab/uid2-android-sdk/blob/ff93ebf597f5de7d440a84f7015a334ba4138ede/sdk/src/main/java/com/uid2/UID2Client.kt#L46 //"ios"/"tvos" is from https://github.com/IABTechLab/uid2-ios-sdk/blob/91c290d29a7093cfc209eca493d1fee80c17e16a/Sources/UID2/UID2Client.swift#L36-L38 private static final List SUPPORTED_IN_APP = Arrays.asList("Android", "ios", "tvos"); @@ -164,7 +168,8 @@ public UIDOperatorVerticle(IConfigStore configStore, IStatsCollectorQueue statsCollectorQueue, SecureLinkValidatorService secureLinkValidatorService, Handler saltRetrievalResponseHandler, - UidInstanceIdProvider uidInstanceIdProvider) { + UidInstanceIdProvider uidInstanceIdProvider, + WorkerExecutor computeWorkerPool) { this.keyManager = keyManager; this.secureLinkValidatorService = secureLinkValidatorService; try { @@ -198,6 +203,8 @@ public UIDOperatorVerticle(IConfigStore configStore, this.identityV3Enabled = config.getBoolean(IdentityV3Prop, false); this.disableOptoutToken = config.getBoolean(DisableOptoutTokenProp, false); this.uidInstanceIdProvider = uidInstanceIdProvider; + this.computeWorkerPool = computeWorkerPool; + this.isAsyncBatchRequestsEnabled = config.getBoolean(EnableAsyncBatchRequestProp, false); } @Override @@ -282,10 +289,6 @@ private void setUpEncryptedRoutes(Router mainRouter, BodyHandler bodyHandler) { rc -> encryptedPayloadHandler.handleTokenRefresh(rc, this::handleTokenRefreshV2))); mainRouter.post(V2_TOKEN_VALIDATE.toString()).handler(bodyHandler).handler(auth.handleV1( rc -> encryptedPayloadHandler.handle(rc, this::handleTokenValidateV2), Role.GENERATOR)); - mainRouter.post(V2_IDENTITY_BUCKETS.toString()).handler(bodyHandler).handler(auth.handleV1( - rc -> encryptedPayloadHandler.handle(rc, this::handleBucketsV2), Role.MAPPER)); - mainRouter.post(V2_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1( - rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV2), Role.MAPPER)); mainRouter.post(V2_KEY_LATEST.toString()).handler(bodyHandler).handler(auth.handleV1( rc -> encryptedPayloadHandler.handle(rc, this::handleKeysRequestV2), Role.ID_READER)); mainRouter.post(V2_KEY_SHARING.toString()).handler(bodyHandler).handler(auth.handleV1( @@ -303,8 +306,22 @@ private void setUpEncryptedRoutes(Router mainRouter, BodyHandler bodyHandler) { if (this.clientSideTokenGenerate) mainRouter.post(V2_TOKEN_CLIENTGENERATE.toString()).handler(bodyHandler).handler(this::handleClientSideTokenGenerate); - mainRouter.post(V3_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1( - rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV3), Role.MAPPER)); + if (isAsyncBatchRequestsEnabled) { + LOGGER.info("Async batch requests enabled"); + mainRouter.post(V2_IDENTITY_BUCKETS.toString()).handler(bodyHandler).handler(auth.handleV1( + rc -> encryptedPayloadHandler.handleAsync(rc, this::handleBucketsV2Async), Role.MAPPER)); + mainRouter.post(V2_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1( + rc -> encryptedPayloadHandler.handleAsync(rc, this::handleIdentityMapV2Async), Role.MAPPER)); + mainRouter.post(V3_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1( + rc -> encryptedPayloadHandler.handleAsync(rc, this::handleIdentityMapV3Async), Role.MAPPER)); + } else { + mainRouter.post(V2_IDENTITY_BUCKETS.toString()).handler(bodyHandler).handler(auth.handleV1( + rc -> encryptedPayloadHandler.handle(rc, this::handleBucketsV2), Role.MAPPER)); + mainRouter.post(V2_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1( + rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV2), Role.MAPPER)); + mainRouter.post(V3_IDENTITY_MAP.toString()).handler(bodyHandler).handler(auth.handleV1( + rc -> encryptedPayloadHandler.handle(rc, this::handleIdentityMapV3), Role.MAPPER)); + } } private void handleClientSideTokenGenerate(RoutingContext rc) { @@ -1037,6 +1054,13 @@ private Future handleLogoutAsyncV2(RoutingContext rc) { } } + private Future handleBucketsV2Async(RoutingContext rc) { + return computeWorkerPool.executeBlocking(() -> { + handleBucketsV2(rc); + return null; + }); + } + private void handleBucketsV2(RoutingContext rc) { final JsonObject req = (JsonObject) rc.data().get("request"); final String qp = req.getString("since_timestamp"); @@ -1222,6 +1246,13 @@ private boolean validateServiceLink(RoutingContext rc) { return false; } + private Future handleIdentityMapV2Async(RoutingContext rc) { + return computeWorkerPool.executeBlocking(() -> { + handleIdentityMapV2(rc); + return null; + }); + } + private void handleIdentityMapV2(RoutingContext rc) { try { final Integer siteId = RoutingContextUtil.getSiteId(rc); @@ -1285,6 +1316,13 @@ private InputUtil.InputVal[] getIdentityMapV2Input(RoutingContext rc) { getInputList.get(); } + private Future handleIdentityMapV3Async(RoutingContext rc) { + return computeWorkerPool.executeBlocking(() -> { + handleIdentityMapV3(rc); + return null; + }); + } + private void handleIdentityMapV3(RoutingContext rc) { try { JsonObject jsonInput = (JsonObject) rc.data().get("request"); diff --git a/src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java b/src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java index 43e06dc6b..79868da94 100644 --- a/src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java +++ b/src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java @@ -11,6 +11,7 @@ import com.uid2.shared.store.*; import com.uid2.shared.store.salt.ISaltProvider; import io.vertx.core.Handler; +import io.vertx.core.WorkerExecutor; import io.vertx.core.json.JsonObject; import java.time.Clock; @@ -33,8 +34,9 @@ public ExtendedUIDOperatorVerticle(IConfigStore configStore, IStatsCollectorQueue statsCollectorQueue, SecureLinkValidatorService secureLinkValidationService, Handler saltRetrievalResponseHandler, - UidInstanceIdProvider uidInstanceIdProvider) { - super(configStore, config, clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, keyManager, saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidationService, saltRetrievalResponseHandler, uidInstanceIdProvider); + UidInstanceIdProvider uidInstanceIdProvider, + WorkerExecutor computeWorkerPool) { + super(configStore, config, clientSideTokenGenerate, siteProvider, clientKeyProvider, clientSideKeypairProvider, keyManager, saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidationService, saltRetrievalResponseHandler, uidInstanceIdProvider, computeWorkerPool); } public IUIDOperatorService getIdService() { diff --git a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java index 835d76d91..e45bd9f46 100644 --- a/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java +++ b/src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java @@ -38,6 +38,7 @@ import io.vertx.core.Future; import io.vertx.core.Handler; import io.vertx.core.Vertx; +import io.vertx.core.WorkerExecutor; import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpHeaders; import io.vertx.core.json.JsonArray; @@ -142,6 +143,7 @@ public class UIDOperatorVerticleTest { private ExtendedUIDOperatorVerticle uidOperatorVerticle; private RuntimeConfig runtimeConfig; private EncryptedTokenEncoder encoder; + private WorkerExecutor computeWorkerPool; @BeforeEach void deployVerticle(Vertx vertx, VertxTestContext testContext, TestInfo testInfo) { @@ -165,7 +167,8 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext, TestInfo testInfo this.uidInstanceIdProvider = new UidInstanceIdProvider("test-instance", "id"); - this.uidOperatorVerticle = new ExtendedUIDOperatorVerticle(configStore, config, config.getBoolean("client_side_token_generate"), siteProvider, clientKeyProvider, clientSideKeypairProvider, new KeyManager(keysetKeyStore, keysetProvider), saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidatorService, shutdownHandler::handleSaltRetrievalResponse, uidInstanceIdProvider); + this.computeWorkerPool = vertx.createSharedWorkerExecutor("compute", 4); + this.uidOperatorVerticle = new ExtendedUIDOperatorVerticle(configStore, config, config.getBoolean("client_side_token_generate"), siteProvider, clientKeyProvider, clientSideKeypairProvider, new KeyManager(keysetKeyStore, keysetProvider), saltProvider, optOutStore, clock, statsCollectorQueue, secureLinkValidatorService, shutdownHandler::handleSaltRetrievalResponse, uidInstanceIdProvider, this.computeWorkerPool); vertx.deployVerticle(uidOperatorVerticle, testContext.succeeding(id -> testContext.completeNow())); this.registry = new SimpleMeterRegistry(); @@ -177,6 +180,9 @@ void deployVerticle(Vertx vertx, VertxTestContext testContext, TestInfo testInfo @AfterEach void teardown() { Metrics.globalRegistry.remove(registry); + if (computeWorkerPool != null) { + computeWorkerPool.close(); + } } private RuntimeConfig setupRuntimeConfig(JsonObject config) {