Skip to content

Commit 60781fc

Browse files
authored
Merge pull request #430 from IABTechLab/aul-UID2-5227-further-tweaking-cloud-key-rotation
Not deleting future keys on rotation since we now pre-create keys. Wr…
2 parents 506b169 + 7f89f4b commit 60781fc

File tree

9 files changed

+50
-53
lines changed

9 files changed

+50
-53
lines changed

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,15 @@
55
import com.uid2.admin.auth.OktaAuthProvider;
66
import com.uid2.admin.auth.AuthProvider;
77
import com.uid2.admin.auth.TokenRefreshHandler;
8-
import com.uid2.admin.cloudencryption.CloudKeyStatePlanner;
9-
import com.uid2.admin.cloudencryption.ExpiredKeyCountRetentionStrategy;
8+
import com.uid2.admin.cloudencryption.*;
109
import com.uid2.admin.job.JobDispatcher;
1110
import com.uid2.admin.job.jobsync.EncryptedFilesSyncJob;
1211
import com.uid2.admin.job.jobsync.PrivateSiteDataSyncJob;
1312
import com.uid2.admin.job.jobsync.keyset.ReplaceSharingTypesWithSitesJob;
1413
import com.uid2.admin.legacy.LegacyClientKeyStoreWriter;
1514
import com.uid2.admin.legacy.RotatingLegacyClientKeyProvider;
1615
import com.uid2.admin.managers.KeysetManager;
17-
import com.uid2.admin.cloudencryption.CloudSecretGenerator;
1816
import com.uid2.admin.monitoring.DataStoreMetrics;
19-
import com.uid2.admin.cloudencryption.CloudEncryptionKeyManager;
2017
import com.uid2.admin.secret.*;
2118
import com.uid2.admin.store.*;
2219
import com.uid2.admin.store.reader.RotatingAdminKeysetStore;
@@ -248,7 +245,7 @@ public void run() {
248245
ClientSideKeypairService clientSideKeypairService = new ClientSideKeypairService(config, auth, writeLock, clientSideKeypairStoreWriter, clientSideKeypairProvider, siteProvider, keysetManager, keypairGenerator, clock);
249246

250247
var cloudEncryptionSecretGenerator = new CloudSecretGenerator(keyGenerator);
251-
var cloudEncryptionKeyRetentionStrategy = new ExpiredKeyCountRetentionStrategy(clock, 10);
248+
var cloudEncryptionKeyRetentionStrategy = new ExpiredKeyCountRetentionStrategy(10);
252249
var cloudEncryptionKeyRotationStrategy = new CloudKeyStatePlanner(cloudEncryptionSecretGenerator, clock, cloudEncryptionKeyRetentionStrategy);
253250
var cloudEncryptionKeyManager = new CloudEncryptionKeyManager(rotatingCloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, operatorKeyProvider, cloudEncryptionKeyRotationStrategy);
254251

@@ -269,7 +266,7 @@ public void run() {
269266
new EncryptedFilesSyncService(auth, jobDispatcher, writeLock, config, rotatingCloudEncryptionKeyProvider),
270267
new JobDispatcherService(auth, jobDispatcher),
271268
new SearchService(auth, clientKeyProvider, operatorKeyProvider),
272-
new CloudEncryptionKeyService(auth, cloudEncryptionKeyManager)
269+
new CloudEncryptionKeyService(auth, cloudEncryptionKeyManager, jobDispatcher)
273270
};
274271

275272

src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManager.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66
import com.uid2.shared.auth.RotatingOperatorKeyProvider;
77
import com.uid2.shared.model.CloudEncryptionKey;
88
import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider;
9-
import io.micrometer.core.instrument.Counter;
10-
import io.micrometer.core.instrument.Metrics;
119
import org.slf4j.Logger;
1210
import org.slf4j.LoggerFactory;
1311

@@ -39,23 +37,15 @@ public CloudEncryptionKeyManager(
3937
// For any site that has an operator create a new key activating in one hour
4038
// Keep up to 10 most recent old keys per site, delete the rest
4139
public void rotateKeys() throws Exception {
42-
boolean success = false;
4340
try {
4441
refreshCloudData();
4542
var desiredKeys = planner.planRotation(existingKeys, operatorKeys);
4643
writeKeys(desiredKeys);
47-
success = true;
4844
var diff = CloudEncryptionKeyDiff.calculateDiff(existingKeys, desiredKeys);
4945
LOGGER.info("Key rotation complete. Diff: {}", diff);
5046
} catch (Exception e) {
51-
success = false;
5247
LOGGER.error("Key rotation failed", e);
5348
throw e;
54-
} finally {
55-
Counter.builder("uid2.cloud_encryption_key_manager.rotations")
56-
.tag("success", Boolean.toString(success))
57-
.description("The number of times rotations have happened")
58-
.register(Metrics.globalRegistry);
5949
}
6050
}
6151

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.uid2.admin.cloudencryption;
2+
3+
import com.uid2.admin.job.model.Job;
4+
5+
public class CloudEncryptionKeyRotationJob extends Job {
6+
private final CloudEncryptionKeyManager keyManager;
7+
8+
public CloudEncryptionKeyRotationJob(CloudEncryptionKeyManager keyManager) {
9+
this.keyManager = keyManager;
10+
}
11+
12+
@Override
13+
public String getId() {
14+
return "cloud-encryption-key-rotation";
15+
}
16+
17+
@Override
18+
public void execute() throws Exception {
19+
keyManager.rotateKeys();
20+
}
21+
}

src/main/java/com/uid2/admin/cloudencryption/ExpiredKeyCountRetentionStrategy.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
11
package com.uid2.admin.cloudencryption;
22

3-
import com.uid2.admin.store.Clock;
43
import com.uid2.shared.model.CloudEncryptionKey;
54

65
import java.util.Comparator;
76
import java.util.Set;
87
import java.util.stream.Collectors;
98

10-
// Only keep keys past activation time - no future keys allowed
119
// Keep up to `expiredKeysToRetain` most recent keys
1210
// Considers all keys passed, doesn't do grouping by site (handled upstream)
1311
public class ExpiredKeyCountRetentionStrategy implements CloudKeyRetentionStrategy {
14-
private final Clock clock;
1512
private final int expiredKeysToRetain;
1613

17-
public ExpiredKeyCountRetentionStrategy(Clock clock, int expiredKeysToRetain) {
18-
this.clock = clock;
14+
public ExpiredKeyCountRetentionStrategy(int expiredKeysToRetain) {
1915
this.expiredKeysToRetain = expiredKeysToRetain;
2016
}
2117

2218
@Override
2319
public Set<CloudEncryptionKey> selectKeysToRetain(Set<CloudEncryptionKey> keysForSite) {
2420
return keysForSite.stream()
25-
.filter(key -> key.getActivates() <= clock.getEpochSecond())
2621
.sorted(Comparator.comparingLong(CloudEncryptionKey::getActivates).reversed())
2722
.limit(expiredKeysToRetain)
2823
.collect(Collectors.toSet());

src/main/java/com/uid2/admin/job/JobDispatcher.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public List<JobInfo> getJobQueueInfo() {
153153
}
154154
jobInfos.addAll(jobQueue.stream()
155155
.map(job -> new JobInfo(job, false))
156-
.collect(Collectors.toList()));
156+
.toList());
157157
}
158158
return jobInfos;
159159
}

src/main/java/com/uid2/admin/vertx/service/CloudEncryptionKeyService.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import com.uid2.admin.auth.AdminAuthMiddleware;
66
import com.uid2.admin.cloudencryption.CloudEncryptionKeyManager;
7+
import com.uid2.admin.cloudencryption.CloudEncryptionKeyRotationJob;
8+
import com.uid2.admin.job.JobDispatcher;
79
import com.uid2.admin.model.CloudEncryptionKeyListResponse;
810
import com.uid2.admin.vertx.Endpoints;
911
import com.uid2.shared.auth.Role;
@@ -15,14 +17,16 @@
1517
public class CloudEncryptionKeyService implements IService {
1618
private final AdminAuthMiddleware auth;
1719
private final CloudEncryptionKeyManager keyManager;
20+
private final JobDispatcher jobDispatcher;
1821
private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance();
1922

2023
public CloudEncryptionKeyService(
2124
AdminAuthMiddleware auth,
22-
CloudEncryptionKeyManager keyManager
23-
) {
25+
CloudEncryptionKeyManager keyManager,
26+
JobDispatcher jobDispatcher) {
2427
this.auth = auth;
2528
this.keyManager = keyManager;
29+
this.jobDispatcher = jobDispatcher;
2630
}
2731

2832
@Override
@@ -38,8 +42,16 @@ public void setupRoutes(Router router) {
3842

3943
private void handleRotate(RoutingContext rc) {
4044
try {
41-
keyManager.rotateKeys();
42-
rc.response().end();
45+
jobDispatcher.enqueue(new CloudEncryptionKeyRotationJob(keyManager));
46+
var isSuccess = jobDispatcher.executeNextJob().get();
47+
if (isSuccess) {
48+
rc.response().end();
49+
} else {
50+
rc.response()
51+
.setStatusCode(500)
52+
.putHeader("Content-Type", "text/plain")
53+
.end("Failed to rotate cloud encryption keys");
54+
}
4355
} catch (Exception e) {
4456
rc.fail(500, e);
4557
}

src/test/java/com/uid2/admin/cloudencryption/CloudKeyStatePlannerTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import org.junit.jupiter.api.BeforeEach;
77
import org.junit.jupiter.api.Test;
88

9-
import java.util.List;
109
import java.util.Set;
1110

1211
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
@@ -29,7 +28,7 @@ void setUp() {
2928
secretGenerator = mock(CloudSecretGenerator.class);
3029
when(secretGenerator.generate()).thenReturn(secret1);
3130
clock = mock(Clock.class);
32-
retentionStrategy = new ExpiredKeyCountRetentionStrategy(clock, 3);
31+
retentionStrategy = new ExpiredKeyCountRetentionStrategy(3);
3332
planner = new CloudKeyStatePlanner(secretGenerator, clock, retentionStrategy);
3433
}
3534

src/test/java/com/uid2/admin/cloudencryption/ExpiredKeyCountRetentionStrategyTest.java

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.uid2.admin.cloudencryption;
22

3-
import com.uid2.admin.store.Clock;
43
import com.uid2.shared.model.CloudEncryptionKey;
54
import org.assertj.core.api.AssertionsForClassTypes;
65
import org.junit.jupiter.api.BeforeEach;
@@ -10,21 +9,16 @@
109
import java.util.Set;
1110

1211
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
13-
import static org.mockito.Mockito.mock;
14-
import static org.mockito.Mockito.when;
1512

1613
class ExpiredKeyCountRetentionStrategyTest {
1714
private final long past1 = 100L;
1815
private final long past2 = 200L;
1916
private final long now = 300L;
20-
private final long future = 400L;
2117
private ExpiredKeyCountRetentionStrategy strategy;
2218

2319
@BeforeEach
2420
void setUp() {
25-
Clock clock = mock(Clock.class);
26-
strategy = new ExpiredKeyCountRetentionStrategy(clock, 2);
27-
when(clock.getEpochSecond()).thenReturn(now);
21+
strategy = new ExpiredKeyCountRetentionStrategy(2);
2822
}
2923

3024
@Test
@@ -47,20 +41,6 @@ void selectKeysToRetain_withLessThanNKeys() {
4741
AssertionsForClassTypes.assertThat(actual).isEqualTo(originalKeys);
4842
}
4943

50-
@Test
51-
void selectKeysToRetain_dropsFutureKeys() {
52-
var activeKey = new CloudEncryptionKey(1, 1, now, now, "secret 1");
53-
var futureKey1 = new CloudEncryptionKey(2, 1, future, now, "secret 2");
54-
var expiredKey1 = new CloudEncryptionKey(3, 1, past1, now, "secret 3");
55-
var originalKeys = Set.of(activeKey, futureKey1, expiredKey1);
56-
57-
var expected = Set.of(activeKey, expiredKey1);
58-
59-
var actual = strategy.selectKeysToRetain(originalKeys);
60-
61-
assertThat(actual).isEqualTo(expected);
62-
}
63-
6444
@Test
6545
void selectKeysToRetain_keepsNMostRecentNonFutureKeys() {
6646
var oldestExpiredKey = new CloudEncryptionKey(1, 1, past1, past1, "secret 1"); // Don't retain

src/test/java/com/uid2/admin/vertx/CloudEncryptionKeyServiceTest.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import com.uid2.admin.cloudencryption.CloudEncryptionKeyManager;
77
import com.uid2.admin.cloudencryption.CloudKeyStatePlanner;
88
import com.uid2.admin.cloudencryption.ExpiredKeyCountRetentionStrategy;
9+
import com.uid2.admin.job.JobDispatcher;
910
import com.uid2.admin.model.CloudEncryptionKeyListResponse;
1011
import com.uid2.admin.model.CloudEncryptionKeySummary;
12+
import com.uid2.admin.store.InstantClock;
1113
import com.uid2.admin.vertx.service.CloudEncryptionKeyService;
1214
import com.uid2.admin.vertx.service.IService;
1315
import com.uid2.admin.vertx.test.ServiceTestBase;
@@ -21,7 +23,6 @@
2123
import io.vertx.junit5.VertxTestContext;
2224
import org.junit.jupiter.api.Test;
2325

24-
import java.util.List;
2526
import java.util.Map;
2627
import java.util.Set;
2728

@@ -49,7 +50,7 @@ public class CloudEncryptionKeyServiceTest extends ServiceTestBase {
4950

5051
@Override
5152
protected IService createService() {
52-
var retentionStrategy = new ExpiredKeyCountRetentionStrategy(clock, 2);
53+
var retentionStrategy = new ExpiredKeyCountRetentionStrategy(2);
5354
var rotationStrategy = new CloudKeyStatePlanner(cloudSecretGenerator, clock, retentionStrategy);
5455
var manager = new CloudEncryptionKeyManager(
5556
cloudEncryptionKeyProvider,
@@ -58,7 +59,9 @@ protected IService createService() {
5859
rotationStrategy
5960
);
6061

61-
return new CloudEncryptionKeyService(auth, manager);
62+
var dispatcher = new JobDispatcher("test-job-dispatcher", 10, 5, new InstantClock());
63+
64+
return new CloudEncryptionKeyService(auth, manager, dispatcher);
6265
}
6366

6467
@Test

0 commit comments

Comments
 (0)