From 86b47c9519c81cb6a78b57822e13b969b5e087c6 Mon Sep 17 00:00:00 2001 From: sophia chen Date: Mon, 14 Jul 2025 15:28:57 +1000 Subject: [PATCH 1/5] added feature switch for default min ages in salt rotation --- src/main/java/com/uid2/admin/AdminConst.java | 1 + .../com/uid2/admin/salt/SaltRotation.java | 6 ++ .../uid2/admin/vertx/service/SaltService.java | 29 ++++++-- .../com/uid2/admin/salt/SaltServiceTest.java | 68 ++++++++++++++++++- 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/uid2/admin/AdminConst.java b/src/main/java/com/uid2/admin/AdminConst.java index c4296d3e..c507cd58 100644 --- a/src/main/java/com/uid2/admin/AdminConst.java +++ b/src/main/java/com/uid2/admin/AdminConst.java @@ -6,4 +6,5 @@ public class AdminConst { public static final String ROLE_OKTA_GROUP_MAP_PRIVILEGED = "role_okta_group_map_privileged"; public static final String ROLE_OKTA_GROUP_MAP_SUPER_USER = "role_okta_group_map_super_user"; public static final String ENABLE_SALT_ROTATION_REFRESH_FROM = "enable_salt_rotation_refresh_from"; + public static final String ENABLE_SALT_ROTATION_CUSTOM_AGE_THRESHOLDS = "enable_salt_rotation_custom_age_thresholds"; } diff --git a/src/main/java/com/uid2/admin/salt/SaltRotation.java b/src/main/java/com/uid2/admin/salt/SaltRotation.java index adcdf151..cd2887d0 100644 --- a/src/main/java/com/uid2/admin/salt/SaltRotation.java +++ b/src/main/java/com/uid2/admin/salt/SaltRotation.java @@ -23,11 +23,17 @@ public class SaltRotation { private final IKeyGenerator keyGenerator; private final boolean isRefreshFromEnabled; + private final boolean isCustomAgeThresholdEnabled; private static final Logger LOGGER = LoggerFactory.getLogger(SaltRotation.class); public SaltRotation(JsonObject config, IKeyGenerator keyGenerator) { this.keyGenerator = keyGenerator; this.isRefreshFromEnabled = config.getBoolean(AdminConst.ENABLE_SALT_ROTATION_REFRESH_FROM, false); + this.isCustomAgeThresholdEnabled = config.getBoolean(AdminConst.ENABLE_SALT_ROTATION_CUSTOM_AGE_THRESHOLDS, false); + } + + public boolean isCustomAgeThresholdEnabled() { + return this.isCustomAgeThresholdEnabled; } public Result rotateSalts( diff --git a/src/main/java/com/uid2/admin/vertx/service/SaltService.java b/src/main/java/com/uid2/admin/vertx/service/SaltService.java index fe9c76f7..3a32d0f0 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SaltService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SaltService.java @@ -1,5 +1,6 @@ package com.uid2.admin.vertx.service; +import com.uid2.admin.AdminConst; import com.uid2.admin.auth.AdminAuthMiddleware; import com.uid2.admin.salt.SaltRotation; import com.uid2.admin.salt.TargetDate; @@ -21,10 +22,7 @@ import java.time.*; import java.time.format.DateTimeFormatter; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import static com.uid2.admin.vertx.Endpoints.*; @@ -36,6 +34,7 @@ public class SaltService implements IService { private final SaltStoreWriter storageManager; private final RotatingSaltProvider saltProvider; private final SaltRotation saltRotation; + private final Duration[] defaultSaltRotationAgeThresholds; public SaltService(AdminAuthMiddleware auth, WriteLock writeLock, @@ -47,6 +46,7 @@ public SaltService(AdminAuthMiddleware auth, this.storageManager = storageManager; this.saltProvider = saltProvider; this.saltRotation = saltRotation; + this.defaultSaltRotationAgeThresholds = generateThresholds(30, 390, 30); } @Override @@ -117,8 +117,15 @@ private void handleSaltRotate(RoutingContext rc) { try { final Optional fraction = RequestUtil.getDouble(rc, "fraction"); if (fraction.isEmpty()) return; - final Duration[] minAges = RequestUtil.getDurations(rc, "min_ages_in_seconds"); - if (minAges == null) return; + + final Duration[] ageThresholds; + if (saltRotation.isCustomAgeThresholdEnabled()) { + ageThresholds = RequestUtil.getDurations(rc, "min_ages_in_seconds"); + if (ageThresholds == null) return; + } else { + ageThresholds = defaultSaltRotationAgeThresholds; + } + final TargetDate targetDate = RequestUtil.getDate(rc, "target_date", DateTimeFormatter.ISO_LOCAL_DATE) @@ -134,7 +141,7 @@ private void handleSaltRotate(RoutingContext rc) { final List snapshots = saltProvider.getSnapshots(); final RotatingSaltProvider.SaltSnapshot lastSnapshot = snapshots.getLast(); - final SaltRotation.Result result = saltRotation.rotateSalts(lastSnapshot, minAges, fraction.get(), targetDate); + final SaltRotation.Result result = saltRotation.rotateSalts(lastSnapshot, ageThresholds, fraction.get(), targetDate); if (!result.hasSnapshot()) { ResponseUtil.error(rc, 200, result.getReason()); return; @@ -151,6 +158,14 @@ private void handleSaltRotate(RoutingContext rc) { } } + private Duration[] generateThresholds(int minAge, int maxAge, int interval) { + List thresholds = new ArrayList<>(); + for (int i = minAge; i <= maxAge; i += interval) { + thresholds.add(Duration.ofDays(i)); + } + return thresholds.toArray(new Duration[0]); + } + private JsonObject toJson(RotatingSaltProvider.SaltSnapshot snapshot) { JsonObject jo = new JsonObject(); jo.put("effective", snapshot.getEffective().toEpochMilli()); diff --git a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java index 7d4755f0..3ed7c97d 100644 --- a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java +++ b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java @@ -11,7 +11,7 @@ import io.vertx.junit5.VertxTestContext; import org.junit.jupiter.api.Test; import org.mockito.Mock; - +import java.time.Duration; import java.time.Instant; import java.util.Arrays; @@ -133,6 +133,72 @@ void rotateSaltsWitnSpecificTargetDate(Vertx vertx, VertxTestContext testContext }); } + @Test + void rotateSaltsWithCustomAgeThresholdsEnabled(Vertx vertx, VertxTestContext testContext) throws Exception { + fakeAuth(Role.SUPER_USER); + + when(saltRotation.isCustomAgeThresholdEnabled()).thenReturn(true); + + final SaltSnapshotBuilder lastSnapshot = SaltSnapshotBuilder.start().effective(daysEarlier(1)).expires(daysLater(6)).entries(1, daysEarlier(1)); + setSnapshots(lastSnapshot); + + var result = SaltRotation.Result.fromSnapshot(SaltSnapshotBuilder.start().effective(targetDate()).expires(daysEarlier(7)).entries(1, targetDate()).build()); + + Duration[] expectedCustomAgeThresholds = new Duration[]{ + Duration.ofSeconds(50), + Duration.ofSeconds(60), + Duration.ofSeconds(70) + }; + + when(saltRotation.rotateSalts(any(), eq(expectedCustomAgeThresholds), eq(0.2), eq(utcTomorrow))).thenReturn(result); + + post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2", "", response -> { + assertEquals(200, response.statusCode()); + testContext.completeNow(); + }); + } + + @Test + void rotateSaltsWithCustomAgeThresholdsDisabled(Vertx vertx, VertxTestContext testContext) throws Exception { + fakeAuth(Role.SUPER_USER); + + when(saltRotation.isCustomAgeThresholdEnabled()).thenReturn(false); + + final SaltSnapshotBuilder lastSnapshot = SaltSnapshotBuilder.start().effective(daysEarlier(1)).expires(daysLater(6)).entries(1, daysEarlier(1)); + setSnapshots(lastSnapshot); + + var result = SaltRotation.Result.fromSnapshot(SaltSnapshotBuilder.start().effective(targetDate()).expires(daysEarlier(7)).entries(1, targetDate()).build()); + + Duration[] expectedDefaultAgeThresholds = new Duration[]{ + Duration.ofDays(30), Duration.ofDays(60), Duration.ofDays(90), Duration.ofDays(120), + Duration.ofDays(150), Duration.ofDays(180), Duration.ofDays(210), Duration.ofDays(240), + Duration.ofDays(270), Duration.ofDays(300), Duration.ofDays(330), Duration.ofDays(360), + Duration.ofDays(390) + }; + + when(saltRotation.rotateSalts(any(), eq(expectedDefaultAgeThresholds), eq(0.2), eq(utcTomorrow))).thenReturn(result); + + post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2", "", response -> { + assertEquals(200, response.statusCode()); + testContext.completeNow(); + }); + } + + @Test + void rotateSaltsWithCustomAgeThresholdsEnabledButMissingParameter(Vertx vertx, VertxTestContext testContext) throws Exception { + fakeAuth(Role.SUPER_USER); + + when(saltRotation.isCustomAgeThresholdEnabled()).thenReturn(true); + + final SaltSnapshotBuilder lastSnapshot = SaltSnapshotBuilder.start().effective(daysEarlier(1)).expires(daysLater(6)).entries(1, daysEarlier(1)); + setSnapshots(lastSnapshot); + + post(vertx, testContext, "api/salt/rotate?fraction=0.2", "", response -> { + assertEquals(400, response.statusCode()); + testContext.completeNow(); + }); + } + private void checkSnapshotsResponse(SaltSnapshotBuilder[] expectedSnapshots, Object[] actualSnapshots) { assertEquals(expectedSnapshots.length, actualSnapshots.length); for (int i = 0; i < expectedSnapshots.length; ++i) { From adbd7185804a0b60c93fb5817a99a978df0e49a3 Mon Sep 17 00:00:00 2001 From: sophia chen Date: Mon, 14 Jul 2025 15:32:41 +1000 Subject: [PATCH 2/5] clean up --- .../java/com/uid2/admin/vertx/service/SaltService.java | 1 - src/test/java/com/uid2/admin/salt/SaltServiceTest.java | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SaltService.java b/src/main/java/com/uid2/admin/vertx/service/SaltService.java index 3a32d0f0..09646d5d 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SaltService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SaltService.java @@ -1,6 +1,5 @@ package com.uid2.admin.vertx.service; -import com.uid2.admin.AdminConst; import com.uid2.admin.auth.AdminAuthMiddleware; import com.uid2.admin.salt.SaltRotation; import com.uid2.admin.salt.TargetDate; diff --git a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java index 3ed7c97d..bb830527 100644 --- a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java +++ b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java @@ -111,7 +111,7 @@ void rotateSaltsNoNewSnapshot(Vertx vertx, VertxTestContext testContext) throws } @Test - void rotateSaltsWitnSpecificTargetDate(Vertx vertx, VertxTestContext testContext) throws Exception { + void rotateSaltsWithSpecificTargetDate(Vertx vertx, VertxTestContext testContext) throws Exception { fakeAuth(Role.SUPER_USER); final SaltSnapshotBuilder[] snapshots = { SaltSnapshotBuilder.start().effective(daysEarlier(5)).expires(daysEarlier(4)).entries(10, daysEarlier(5)), @@ -158,8 +158,8 @@ void rotateSaltsWithCustomAgeThresholdsEnabled(Vertx vertx, VertxTestContext tes }); } - @Test - void rotateSaltsWithCustomAgeThresholdsDisabled(Vertx vertx, VertxTestContext testContext) throws Exception { + @Test + void rotateSaltsWithDefaultAgeThresholds(Vertx vertx, VertxTestContext testContext) throws Exception { fakeAuth(Role.SUPER_USER); when(saltRotation.isCustomAgeThresholdEnabled()).thenReturn(false); @@ -184,8 +184,8 @@ void rotateSaltsWithCustomAgeThresholdsDisabled(Vertx vertx, VertxTestContext te }); } - @Test - void rotateSaltsWithCustomAgeThresholdsEnabledButMissingParameter(Vertx vertx, VertxTestContext testContext) throws Exception { + @Test + void rotateSaltsWithCustomAgeThresholdsEnabledButMissingParameter(Vertx vertx, VertxTestContext testContext) { fakeAuth(Role.SUPER_USER); when(saltRotation.isCustomAgeThresholdEnabled()).thenReturn(true); From 69b5892536c5344ba0e46fba0f499e0e9c261735 Mon Sep 17 00:00:00 2001 From: sophia chen Date: Tue, 15 Jul 2025 10:06:57 +1000 Subject: [PATCH 3/5] made default threshold ages static --- .../uid2/admin/vertx/service/SaltService.java | 30 +++++++++++-------- .../com/uid2/admin/salt/SaltServiceTest.java | 3 ++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SaltService.java b/src/main/java/com/uid2/admin/vertx/service/SaltService.java index 09646d5d..0aa9f80a 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SaltService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SaltService.java @@ -27,13 +27,28 @@ public class SaltService implements IService { private static final Logger LOGGER = LoggerFactory.getLogger(SaltService.class); + private static final Duration[] SALT_ROTATION_AGE_THRESHOLDS = new Duration[]{ + Duration.ofDays(30), + Duration.ofDays(60), + Duration.ofDays(90), + Duration.ofDays(120), + Duration.ofDays(150), + Duration.ofDays(180), + Duration.ofDays(210), + Duration.ofDays(240), + Duration.ofDays(270), + Duration.ofDays(300), + Duration.ofDays(330), + Duration.ofDays(360), + Duration.ofDays(390) + }; private final AdminAuthMiddleware auth; private final WriteLock writeLock; private final SaltStoreWriter storageManager; private final RotatingSaltProvider saltProvider; private final SaltRotation saltRotation; - private final Duration[] defaultSaltRotationAgeThresholds; + public SaltService(AdminAuthMiddleware auth, WriteLock writeLock, @@ -45,7 +60,6 @@ public SaltService(AdminAuthMiddleware auth, this.storageManager = storageManager; this.saltProvider = saltProvider; this.saltRotation = saltRotation; - this.defaultSaltRotationAgeThresholds = generateThresholds(30, 390, 30); } @Override @@ -122,9 +136,9 @@ private void handleSaltRotate(RoutingContext rc) { ageThresholds = RequestUtil.getDurations(rc, "min_ages_in_seconds"); if (ageThresholds == null) return; } else { - ageThresholds = defaultSaltRotationAgeThresholds; + ageThresholds = SALT_ROTATION_AGE_THRESHOLDS; } - + LOGGER.info("Salt rotation age thresholds: {}", Arrays.toString(ageThresholds)); final TargetDate targetDate = RequestUtil.getDate(rc, "target_date", DateTimeFormatter.ISO_LOCAL_DATE) @@ -157,14 +171,6 @@ private void handleSaltRotate(RoutingContext rc) { } } - private Duration[] generateThresholds(int minAge, int maxAge, int interval) { - List thresholds = new ArrayList<>(); - for (int i = minAge; i <= maxAge; i += interval) { - thresholds.add(Duration.ofDays(i)); - } - return thresholds.toArray(new Duration[0]); - } - private JsonObject toJson(RotatingSaltProvider.SaltSnapshot snapshot) { JsonObject jo = new JsonObject(); jo.put("effective", snapshot.getEffective().toEpochMilli()); diff --git a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java index bb830527..508d3ab2 100644 --- a/src/test/java/com/uid2/admin/salt/SaltServiceTest.java +++ b/src/test/java/com/uid2/admin/salt/SaltServiceTest.java @@ -153,6 +153,7 @@ void rotateSaltsWithCustomAgeThresholdsEnabled(Vertx vertx, VertxTestContext tes when(saltRotation.rotateSalts(any(), eq(expectedCustomAgeThresholds), eq(0.2), eq(utcTomorrow))).thenReturn(result); post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2", "", response -> { + verify(saltRotation).rotateSalts(any(), eq(expectedCustomAgeThresholds), eq(0.2), eq(utcTomorrow)); assertEquals(200, response.statusCode()); testContext.completeNow(); }); @@ -179,6 +180,7 @@ void rotateSaltsWithDefaultAgeThresholds(Vertx vertx, VertxTestContext testConte when(saltRotation.rotateSalts(any(), eq(expectedDefaultAgeThresholds), eq(0.2), eq(utcTomorrow))).thenReturn(result); post(vertx, testContext, "api/salt/rotate?min_ages_in_seconds=50,60,70&fraction=0.2", "", response -> { + verify(saltRotation).rotateSalts(any(), eq(expectedDefaultAgeThresholds), eq(0.2), eq(utcTomorrow)); assertEquals(200, response.statusCode()); testContext.completeNow(); }); @@ -194,6 +196,7 @@ void rotateSaltsWithCustomAgeThresholdsEnabledButMissingParameter(Vertx vertx, V setSnapshots(lastSnapshot); post(vertx, testContext, "api/salt/rotate?fraction=0.2", "", response -> { + verify(saltRotation, never()).rotateSalts(any(), any(), anyDouble(), any()); assertEquals(400, response.statusCode()); testContext.completeNow(); }); From cc55e0abca567bbda28df30d991b84c2c1766afc Mon Sep 17 00:00:00 2001 From: sophia chen Date: Tue, 15 Jul 2025 10:22:50 +1000 Subject: [PATCH 4/5] fixed age threshold log line --- src/main/java/com/uid2/admin/vertx/service/SaltService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/uid2/admin/vertx/service/SaltService.java b/src/main/java/com/uid2/admin/vertx/service/SaltService.java index 0aa9f80a..05862cda 100644 --- a/src/main/java/com/uid2/admin/vertx/service/SaltService.java +++ b/src/main/java/com/uid2/admin/vertx/service/SaltService.java @@ -22,6 +22,7 @@ import java.time.*; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; import static com.uid2.admin.vertx.Endpoints.*; @@ -138,7 +139,7 @@ private void handleSaltRotate(RoutingContext rc) { } else { ageThresholds = SALT_ROTATION_AGE_THRESHOLDS; } - LOGGER.info("Salt rotation age thresholds: {}", Arrays.toString(ageThresholds)); + LOGGER.info("Salt rotation age thresholds in seconds: {}", Arrays.stream(ageThresholds).map(Duration::toSeconds).collect(Collectors.toList())); final TargetDate targetDate = RequestUtil.getDate(rc, "target_date", DateTimeFormatter.ISO_LOCAL_DATE) From 5fb89721b7f7cc5e17078da634f6c1aa9ce647d8 Mon Sep 17 00:00:00 2001 From: Release Workflow Date: Wed, 16 Jul 2025 04:09:43 +0000 Subject: [PATCH 5/5] [CI Pipeline] Released Snapshot version: 6.9.1-alpha-202-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 014aed45..59fc0859 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-admin - 6.9.0 + 6.9.1-alpha-202-SNAPSHOT UTF-8