Skip to content

Commit eb5d42f

Browse files
Merge pull request #473 from IABTechLab/sch-UID2-5352-salt-rotation-honour-refresh-from
sch-UID2-5352 honouring refresh from in salt rotation
2 parents f279482 + e3ac510 commit eb5d42f

File tree

6 files changed

+70
-9
lines changed

6 files changed

+70
-9
lines changed

conf/default-config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"enable_keysets": false
2+
"enable_keysets": false,
3+
"enable_salt_rotation_refresh_from": false
34
}

conf/local-config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"keys_acl_metadata_path": "keys_acl/metadata.json",
1313
"salts_metadata_path": "salts/metadata.json",
1414
"salt_snapshot_location_prefix": "salts/salts.txt.",
15+
"enable_salt_rotation_refresh_from": false,
1516
"operators_metadata_path": "operators/metadata.json",
1617
"enclaves_metadata_path": "enclaves/metadata.json",
1718
"partners_metadata_path": "partners/metadata.json",

src/main/java/com/uid2/admin/AdminConst.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ public class AdminConst {
55
public static final String ROLE_OKTA_GROUP_MAP_MAINTAINER = "role_okta_group_map_maintainer";
66
public static final String ROLE_OKTA_GROUP_MAP_PRIVILEGED = "role_okta_group_map_privileged";
77
public static final String ROLE_OKTA_GROUP_MAP_SUPER_USER = "role_okta_group_map_super_user";
8+
public static final String ENABLE_SALT_ROTATION_REFRESH_FROM = "enable_salt_rotation_refresh_from";
89
}

src/main/java/com/uid2/admin/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public void run() {
232232
WriteLock writeLock = new WriteLock();
233233
KeyHasher keyHasher = new KeyHasher();
234234
IKeypairGenerator keypairGenerator = new SecureKeypairGenerator();
235-
SaltRotation saltRotation = new SaltRotation(keyGenerator);
235+
SaltRotation saltRotation = new SaltRotation(config, keyGenerator);
236236
EncryptionKeyService encryptionKeyService = new EncryptionKeyService(
237237
config, auth, writeLock, encryptionKeyStoreWriter, keysetKeyStoreWriter, keyProvider, keysetKeysProvider, adminKeysetProvider, adminKeysetStoreWriter, keyGenerator, clock);
238238
KeysetManager keysetManager = new KeysetManager(

src/main/java/com/uid2/admin/secret/SaltRotation.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.uid2.admin.secret;
22

3+
import com.uid2.admin.AdminConst;
34
import com.uid2.shared.model.SaltEntry;
45
import com.uid2.shared.secret.IKeyGenerator;
56
import com.uid2.shared.store.salt.RotatingSaltProvider;
67

78
import com.uid2.shared.store.salt.RotatingSaltProvider.SaltSnapshot;
9+
import io.vertx.core.json.JsonObject;
810

911
import java.time.Duration;
1012
import java.time.Instant;
@@ -17,12 +19,15 @@
1719
import static java.util.stream.Collectors.toList;
1820

1921
public class SaltRotation {
22+
private final static long THIRTY_DAYS_IN_MS = Duration.ofDays(30).toMillis();
23+
private final static long DAY_IN_MS = Duration.ofDays(1).toMillis();
24+
2025
private final IKeyGenerator keyGenerator;
21-
private final long THIRTY_DAYS_IN_MS = Duration.ofDays(30).toMillis();
22-
private final long DAY_IN_MS = Duration.ofDays(1).toMillis();
26+
private final boolean isRefreshFromEnabled;
2327

24-
public SaltRotation(IKeyGenerator keyGenerator) {
28+
public SaltRotation(JsonObject config, IKeyGenerator keyGenerator) {
2529
this.keyGenerator = keyGenerator;
30+
this.isRefreshFromEnabled = config.getBoolean(AdminConst.ENABLE_SALT_ROTATION_REFRESH_FROM, false);
2631
}
2732

2833
public Result rotateSalts(RotatingSaltProvider.SaltSnapshot lastSnapshot,
@@ -105,14 +110,15 @@ private List<Integer> pickSaltIndexesToRotate(
105110
.sorted()
106111
.toArray(Instant[]::new);
107112
final int maxSalts = (int) Math.ceil(lastSnapshot.getAllRotatingSalts().length * fraction);
113+
final SaltEntry[] rotatableSalts = getRotatableSalts(lastSnapshot, nextEffective.toEpochMilli());
108114
final List<Integer> indexesToRotate = new ArrayList<>();
109115

110116
Instant minLastUpdated = Instant.ofEpochMilli(0);
111117
for (Instant threshold : thresholds) {
112118
if (indexesToRotate.size() >= maxSalts) break;
113119
addIndexesToRotate(
114120
indexesToRotate,
115-
lastSnapshot,
121+
rotatableSalts,
116122
minLastUpdated.toEpochMilli(),
117123
threshold.toEpochMilli(),
118124
maxSalts - indexesToRotate.size()
@@ -122,12 +128,20 @@ private List<Integer> pickSaltIndexesToRotate(
122128
return indexesToRotate;
123129
}
124130

131+
private SaltEntry[] getRotatableSalts(SaltSnapshot lastSnapshot, long nextEffective) {
132+
SaltEntry[] salts = lastSnapshot.getAllRotatingSalts();
133+
if (isRefreshFromEnabled) {
134+
return Arrays.stream(salts).filter(s -> s.refreshFrom() == nextEffective).toArray(SaltEntry[]::new);
135+
}
136+
return salts;
137+
}
138+
139+
125140
private void addIndexesToRotate(List<Integer> entryIndexes,
126-
SaltSnapshot lastSnapshot,
141+
SaltEntry[] entries,
127142
long minLastUpdated,
128143
long maxLastUpdated,
129144
int maxIndexes) {
130-
final SaltEntry[] entries = lastSnapshot.getAllRotatingSalts();
131145
final List<Integer> candidateIndexes = IntStream.range(0, entries.length)
132146
.filter(i -> isBetween(entries[i].lastUpdated(), minLastUpdated, maxLastUpdated))
133147
.boxed()

src/test/java/com/uid2/admin/secret/SaltRotationTest.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.uid2.admin.secret;
22

3+
import com.uid2.admin.AdminConst;
34
import com.uid2.shared.model.SaltEntry;
45
import com.uid2.shared.secret.IKeyGenerator;
56
import com.uid2.shared.store.salt.RotatingSaltProvider;
7+
import io.vertx.core.json.JsonObject;
68
import org.junit.jupiter.api.BeforeEach;
79
import org.junit.jupiter.api.Test;
810
import org.junit.jupiter.params.ParameterizedTest;
@@ -38,7 +40,9 @@ private Instant daysLater(int days) {
3840
void setup() {
3941
MockitoAnnotations.openMocks(this);
4042

41-
saltRotation = new SaltRotation(keyGenerator);
43+
JsonObject config = new JsonObject();
44+
45+
saltRotation = new SaltRotation(config, keyGenerator);
4246
}
4347

4448
private static class SnapshotBuilder {
@@ -318,4 +322,44 @@ void rotateSaltsRemovePreviousSaltsOver90DaysOld() throws Exception {
318322
assertNull(salts[1].previousSalt());
319323
}
320324

325+
326+
@Test
327+
void rotateSaltsRotateWhenRefreshFromIsTargetDate() throws Exception {
328+
JsonObject config = new JsonObject();
329+
config.put(AdminConst.ENABLE_SALT_ROTATION_REFRESH_FROM, Boolean.TRUE);
330+
saltRotation = new SaltRotation(config, keyGenerator);
331+
332+
final Duration[] minAges = {
333+
Duration.ofDays(90),
334+
Duration.ofDays(60),
335+
};
336+
337+
var validForRotation1 = daysEarlier(120).toEpochMilli();
338+
var validForRotation2 = daysEarlier(70).toEpochMilli();
339+
var notValidForRotation = daysEarlier(30).toEpochMilli();
340+
var refreshNow = targetDateAsInstant.toEpochMilli();
341+
var refreshLater = daysLater(20).toEpochMilli();
342+
343+
var lastSnapshot = SnapshotBuilder.start()
344+
.withEntries(
345+
new SaltEntry(1, "1", validForRotation1, "salt", refreshNow, null, null, null),
346+
new SaltEntry(2, "2", notValidForRotation, "salt", refreshNow, null, null, null),
347+
new SaltEntry(3, "3", validForRotation2, "salt", refreshLater, null, null, null)
348+
)
349+
.build(daysEarlier(1), daysLater(6));
350+
351+
var result = saltRotation.rotateSalts(lastSnapshot, minAges, 1, targetDate);
352+
assertTrue(result.hasSnapshot());
353+
354+
var salts = result.getSnapshot().getAllRotatingSalts();
355+
356+
assertEquals(targetDateAsInstant.toEpochMilli(), salts[0].lastUpdated());
357+
assertEquals(daysLater(30).toEpochMilli(), salts[0].refreshFrom());
358+
359+
assertEquals(notValidForRotation, salts[1].lastUpdated());
360+
assertEquals(daysLater(30).toEpochMilli(), salts[1].refreshFrom());
361+
362+
assertEquals(validForRotation2, salts[2].lastUpdated());
363+
assertEquals(refreshLater, salts[2].refreshFrom());
364+
}
321365
}

0 commit comments

Comments
 (0)