diff --git a/pom.xml b/pom.xml
index 3e598314..37f83aa4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.uid2
uid2-admin
- 5.18.7
+ 5.18.8-alpha-127-SNAPSHOT
UTF-8
diff --git a/src/main/java/com/uid2/admin/Main.java b/src/main/java/com/uid2/admin/Main.java
index a5cfebf4..070c0f3d 100644
--- a/src/main/java/com/uid2/admin/Main.java
+++ b/src/main/java/com/uid2/admin/Main.java
@@ -5,7 +5,7 @@
import com.uid2.admin.auth.OktaAuthProvider;
import com.uid2.admin.auth.AuthProvider;
import com.uid2.admin.auth.TokenRefreshHandler;
-import com.uid2.admin.cloudencryption.CloudKeyRotationStrategy;
+import com.uid2.admin.cloudencryption.CloudKeyStatePlanner;
import com.uid2.admin.cloudencryption.ExpiredKeyCountRetentionStrategy;
import com.uid2.admin.job.JobDispatcher;
import com.uid2.admin.job.jobsync.EncryptedFilesSyncJob;
@@ -248,9 +248,9 @@ public void run() {
ClientSideKeypairService clientSideKeypairService = new ClientSideKeypairService(config, auth, writeLock, clientSideKeypairStoreWriter, clientSideKeypairProvider, siteProvider, keysetManager, keypairGenerator, clock);
var cloudEncryptionSecretGenerator = new CloudSecretGenerator(keyGenerator);
- var cloudEncryptionKeyManager = new CloudEncryptionKeyManager(rotatingCloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, cloudEncryptionSecretGenerator);
var cloudEncryptionKeyRetentionStrategy = new ExpiredKeyCountRetentionStrategy(clock, 5);
- var cloudEncryptionKeyRotationStrategy = new CloudKeyRotationStrategy(cloudEncryptionSecretGenerator, clock, cloudEncryptionKeyRetentionStrategy);
+ var cloudEncryptionKeyRotationStrategy = new CloudKeyStatePlanner(cloudEncryptionSecretGenerator, clock, cloudEncryptionKeyRetentionStrategy);
+ var cloudEncryptionKeyManager = new CloudEncryptionKeyManager(rotatingCloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, operatorKeyProvider, cloudEncryptionKeyRotationStrategy);
IService[] services = {
new ClientKeyService(config, auth, writeLock, clientKeyStoreWriter, clientKeyProvider, siteProvider, keysetManager, keyGenerator, keyHasher),
@@ -268,7 +268,7 @@ public void run() {
new PrivateSiteDataRefreshService(auth, jobDispatcher, writeLock, config, rotatingCloudEncryptionKeyProvider),
new JobDispatcherService(auth, jobDispatcher),
new SearchService(auth, clientKeyProvider, operatorKeyProvider),
- new CloudEncryptionKeyService(auth, rotatingCloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, operatorKeyProvider, cloudEncryptionKeyRotationStrategy)
+ new CloudEncryptionKeyService(auth, cloudEncryptionKeyManager)
};
@@ -293,11 +293,7 @@ public void run() {
}
synchronized (writeLock) {
- cloudEncryptionKeyManager.generateKeysForOperators(
- operatorKeyProvider.getAll(),
- config.getLong("cloud_encryption_key_activates_in_seconds"),
- config.getInteger("cloud_encryption_key_count_per_site")
- );
+ cloudEncryptionKeyManager.backfillKeys();
rotatingCloudEncryptionKeyProvider.loadContent();
}
diff --git a/src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyDiff.java b/src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyDiff.java
new file mode 100644
index 00000000..7e68cb7f
--- /dev/null
+++ b/src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyDiff.java
@@ -0,0 +1,46 @@
+package com.uid2.admin.cloudencryption;
+
+import com.uid2.shared.model.CloudEncryptionKey;
+
+import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.Set;
+
+public record CloudEncryptionKeyDiff(
+ int before,
+ int after,
+ int created,
+ int removed,
+ int unchanged
+) {
+ public static CloudEncryptionKeyDiff calculateDiff(Set keysBefore, Set keysAfter) {
+ var before = keysBefore.size();
+ var after = keysAfter.size();
+
+ var intersection = new HashSet<>(keysBefore);
+ intersection.retainAll(keysAfter);
+ int unchanged = intersection.size();
+
+ var onlyInLeft = new HashSet<>(keysBefore);
+ onlyInLeft.removeAll(keysAfter);
+ int removed = onlyInLeft.size();
+
+ var onlyInRight = new HashSet<>(keysAfter);
+ onlyInRight.removeAll(keysBefore);
+ int created = onlyInRight.size();
+
+ return new CloudEncryptionKeyDiff(before, after, created, removed, unchanged);
+ }
+
+ @Override
+ public String toString() {
+ return MessageFormat.format(
+ "before={0}, after={1}, created={2}, removed={3}, unchanged={4}",
+ before,
+ after,
+ created,
+ removed,
+ unchanged
+ );
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManager.java b/src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManager.java
index d2b6a90b..1552f3a5 100644
--- a/src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManager.java
+++ b/src/main/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManager.java
@@ -1,109 +1,95 @@
package com.uid2.admin.cloudencryption;
+import com.uid2.admin.model.CloudEncryptionKeySummary;
import com.uid2.admin.store.writer.CloudEncryptionKeyStoreWriter;
import com.uid2.shared.auth.OperatorKey;
+import com.uid2.shared.auth.RotatingOperatorKeyProvider;
import com.uid2.shared.model.CloudEncryptionKey;
import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider;
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.Metrics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.time.Instant;
import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
public class CloudEncryptionKeyManager {
+ private final RotatingCloudEncryptionKeyProvider keyProvider;
+ private final RotatingOperatorKeyProvider operatorKeyProvider;
+ private final CloudEncryptionKeyStoreWriter keyWriter;
+ private final CloudKeyStatePlanner planner;
+ private Set operatorKeys;
+ private Set existingKeys;
private static final Logger LOGGER = LoggerFactory.getLogger(CloudEncryptionKeyManager.class);
- private final RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider;
- private final CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter;
- private final CloudSecretGenerator secretGenerator;
-
public CloudEncryptionKeyManager(
- RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider,
- CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter,
- CloudSecretGenerator keyGenerator
- ) {
- this.RotatingCloudEncryptionKeyProvider = RotatingCloudEncryptionKeyProvider;
- this.cloudEncryptionKeyStoreWriter = cloudEncryptionKeyStoreWriter;
- this.secretGenerator = keyGenerator;
- }
-
- // Ensures there are `keyCountPerSite` sites for each site corresponding of operatorKeys. If there are less - create new ones.
- // Give all new keys for each site `activationInterval` seconds between activations, starting now
- public void generateKeysForOperators(
- Collection operatorKeys,
- long activationInterval,
- int keyCountPerSite
- ) throws Exception {
- this.RotatingCloudEncryptionKeyProvider.loadContent();
-
- if (operatorKeys == null || operatorKeys.isEmpty()) {
- throw new IllegalArgumentException("Operator keys collection must not be null or empty");
- }
- if (activationInterval <= 0) {
- throw new IllegalArgumentException("Key activate interval must be greater than zero");
- }
- if (keyCountPerSite <= 0) {
- throw new IllegalArgumentException("Key count per site must be greater than zero");
- }
-
- for (Integer siteId : uniqueSiteIdsForOperators(operatorKeys)) {
- ensureEnoughKeysForSite(activationInterval, keyCountPerSite, siteId);
- }
+ RotatingCloudEncryptionKeyProvider keyProvider,
+ CloudEncryptionKeyStoreWriter keyWriter,
+ RotatingOperatorKeyProvider operatorKeyProvider,
+ CloudKeyStatePlanner planner) {
+ this.keyProvider = keyProvider;
+ this.operatorKeyProvider = operatorKeyProvider;
+ this.keyWriter = keyWriter;
+ this.planner = planner;
}
- private void ensureEnoughKeysForSite(long activationInterval, int keyCountPerSite, Integer siteId) throws Exception {
- // Check if the site ID already exists in the S3 key provider and has fewer than the required number of keys
- int currentKeyCount = countKeysForSite(siteId);
- if (currentKeyCount >= keyCountPerSite) {
- LOGGER.info("Site ID {} already has the required number of keys. Skipping key generation.", siteId);
- return;
- }
-
- int keysToGenerate = keyCountPerSite - currentKeyCount;
- for (int i = 0; i < keysToGenerate; i++) {
- addKey(activationInterval, siteId, i);
+ // For any site that has an operator create a new key activating now
+ // Keep up to 5 most recent old keys per site, delete the rest
+ public void rotateKeys() throws Exception {
+ boolean success = false;
+ try {
+ refreshCloudData();
+ var desiredKeys = planner.planRotation(existingKeys, operatorKeys);
+ writeKeys(desiredKeys);
+ success = true;
+ var diff = CloudEncryptionKeyDiff.calculateDiff(existingKeys, desiredKeys);
+ LOGGER.info("Key rotation complete. Diff: {}", diff);
+ } catch (Exception e) {
+ success = false;
+ LOGGER.error("Key rotation failed", e);
+ throw e;
+ } finally {
+ Counter.builder("uid2.cloud_encryption_key_manager.rotations")
+ .tag("success", Boolean.toString(success))
+ .description("The number of times rotations have happened")
+ .register(Metrics.globalRegistry);
}
- LOGGER.info("Generated {} keys for site ID {}", keysToGenerate, siteId);
- }
-
- private void addKey(long keyActivateInterval, Integer siteId, int keyIndex) throws Exception {
- long created = Instant.now().getEpochSecond();
- long activated = created + (keyIndex * keyActivateInterval);
- CloudEncryptionKey cloudEncryptionKey = generateCloudEncryptionKey(siteId, activated, created);
- addCloudEncryptionKey(cloudEncryptionKey);
}
- private static Set uniqueSiteIdsForOperators(Collection operatorKeys) {
- Set uniqueSiteIds = new HashSet<>();
- for (OperatorKey operatorKey : operatorKeys) {
- uniqueSiteIds.add(operatorKey.getSiteId());
+ // For any site that has an operator, if there are no keys, create a key activating now
+ public void backfillKeys() throws Exception {
+ try {
+ refreshCloudData();
+ var desiredKeys = planner.planBackfill(existingKeys, operatorKeys);
+ writeKeys(desiredKeys);
+ var diff = CloudEncryptionKeyDiff.calculateDiff(existingKeys, desiredKeys);
+ LOGGER.info("Key backfill complete. Diff: {}", diff);
+ } catch (Exception e) {
+ LOGGER.error("Key backfill failed", e);
+ throw e;
}
- return uniqueSiteIds;
- }
-
- CloudEncryptionKey generateCloudEncryptionKey(int siteId, long activates, long created) throws Exception {
- int newKeyId = getNextKeyId();
- String secret = secretGenerator.generate();
- return new CloudEncryptionKey(newKeyId, siteId, activates, created, secret);
}
- void addCloudEncryptionKey(CloudEncryptionKey cloudEncryptionKey) throws Exception {
- Map cloudEncryptionKeys = new HashMap<>(RotatingCloudEncryptionKeyProvider.getAll());
- cloudEncryptionKeys.put(cloudEncryptionKey.getId(), cloudEncryptionKey);
- cloudEncryptionKeyStoreWriter.upload(cloudEncryptionKeys, null);
+ public Set getKeySummaries() throws Exception {
+ refreshCloudData();
+ return existingKeys.stream().map(CloudEncryptionKeySummary::fromFullKey).collect(Collectors.toSet());
}
- int getNextKeyId() {
- Map cloudEncryptionKeys = RotatingCloudEncryptionKeyProvider.getAll();
- if (cloudEncryptionKeys == null || cloudEncryptionKeys.isEmpty()) {
- return 1;
- }
- return cloudEncryptionKeys.keySet().stream().max(Integer::compareTo).orElse(0) + 1;
+ private void writeKeys(Set desiredKeys) throws Exception {
+ var keysForWriting = desiredKeys.stream().collect(Collectors.toMap(
+ CloudEncryptionKey::getId,
+ Function.identity())
+ );
+ keyWriter.upload(keysForWriting, null);
}
- int countKeysForSite(int siteId) {
- Map allKeys = RotatingCloudEncryptionKeyProvider.getAll();
- return (int) allKeys.values().stream().filter(key -> key.getSiteId() == siteId).count();
+ private void refreshCloudData() throws Exception {
+ keyProvider.loadContent();
+ operatorKeyProvider.loadContent(operatorKeyProvider.getMetadata());
+ operatorKeys = new HashSet<>(operatorKeyProvider.getAll());
+ existingKeys = new HashSet<>(keyProvider.getAll().values());
}
}
\ No newline at end of file
diff --git a/src/main/java/com/uid2/admin/cloudencryption/CloudKeyRotationStrategy.java b/src/main/java/com/uid2/admin/cloudencryption/CloudKeyStatePlanner.java
similarity index 54%
rename from src/main/java/com/uid2/admin/cloudencryption/CloudKeyRotationStrategy.java
rename to src/main/java/com/uid2/admin/cloudencryption/CloudKeyStatePlanner.java
index fec1f636..8dc1bb1c 100644
--- a/src/main/java/com/uid2/admin/cloudencryption/CloudKeyRotationStrategy.java
+++ b/src/main/java/com/uid2/admin/cloudencryption/CloudKeyStatePlanner.java
@@ -5,18 +5,20 @@
import com.uid2.shared.auth.OperatorKey;
import com.uid2.shared.model.CloudEncryptionKey;
+import java.text.MessageFormat;
import java.util.Collection;
+import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class CloudKeyRotationStrategy {
+public class CloudKeyStatePlanner {
private final CloudSecretGenerator secretGenerator;
private final Clock clock;
private final CloudKeyRetentionStrategy keyRetentionStrategy;
- public CloudKeyRotationStrategy(
+ public CloudKeyStatePlanner(
CloudSecretGenerator secretGenerator,
Clock clock,
CloudKeyRetentionStrategy keyRetentionStrategy
@@ -26,24 +28,32 @@ public CloudKeyRotationStrategy(
this.keyRetentionStrategy = keyRetentionStrategy;
}
- public Set computeDesiredKeys(
- Collection existingCloudKeys,
- Collection operatorKeys
+ public Set planRotation(
+ Set existingKeys,
+ Set operatorKeys
) {
- var keyGenerator = new CloudEncryptionKeyGenerator(clock, secretGenerator, existingCloudKeys);
- Map> existingKeysBySite = existingCloudKeys
+ var keyGenerator = new CloudEncryptionKeyGenerator(clock, secretGenerator, existingKeys);
+ Map> existingKeysBySite = existingKeys
.stream()
.collect(Collectors.groupingBy(CloudEncryptionKey::getSiteId, Collectors.toSet()));
- return operatorKeys
- .stream()
- .map(OperatorKey::getSiteId)
- .distinct()
- .flatMap(siteId -> desiredKeysForSite(siteId, keyGenerator, existingKeysBySite.getOrDefault(siteId, Set.of())))
+ return siteIdsWithOperators(operatorKeys)
+ .flatMap(siteId -> planRotationForSite(siteId, keyGenerator, existingKeysBySite.getOrDefault(siteId, Set.of())))
.collect(Collectors.toSet());
}
- private Stream desiredKeysForSite(
+ public Set planBackfill(
+ Set existingKeys,
+ Set operatorKeys
+ ) {
+ var keyGenerator = new CloudEncryptionKeyGenerator(clock, secretGenerator, existingKeys);
+ var siteIdsWithKeys = existingKeys.stream().map(CloudEncryptionKey::getSiteId).collect(Collectors.toSet());
+ var sitesWithoutKeys = siteIdsWithOperators(operatorKeys).filter(siteId -> !siteIdsWithKeys.contains(siteId));
+ var newKeys = sitesWithoutKeys.map(keyGenerator::makeNewKey);
+ return Streams.concat(existingKeys.stream(), newKeys).collect(Collectors.toSet());
+ }
+
+ private Stream planRotationForSite(
Integer siteId,
CloudEncryptionKeyGenerator keyGenerator,
Set existingKeys
@@ -52,4 +62,11 @@ private Stream desiredKeysForSite(
var newKey = keyGenerator.makeNewKey(siteId);
return Streams.concat(existingKeysToRetain.stream(), Stream.of(newKey));
}
+
+ private static Stream siteIdsWithOperators(Collection operatorKeys) {
+ return operatorKeys
+ .stream()
+ .map(OperatorKey::getSiteId)
+ .distinct();
+ }
}
diff --git a/src/main/java/com/uid2/admin/model/CloudEncryptionKeyListResponse.java b/src/main/java/com/uid2/admin/model/CloudEncryptionKeyListResponse.java
index e62b4899..09e549ce 100644
--- a/src/main/java/com/uid2/admin/model/CloudEncryptionKeyListResponse.java
+++ b/src/main/java/com/uid2/admin/model/CloudEncryptionKeyListResponse.java
@@ -2,9 +2,9 @@
import com.fasterxml.jackson.annotation.JsonProperty;
-import java.util.List;
+import java.util.Set;
public record CloudEncryptionKeyListResponse(
- @JsonProperty List cloudEncryptionKeys
+ @JsonProperty Set cloudEncryptionKeys
) {}
diff --git a/src/main/java/com/uid2/admin/vertx/service/CloudEncryptionKeyService.java b/src/main/java/com/uid2/admin/vertx/service/CloudEncryptionKeyService.java
index f98add26..83e8a939 100644
--- a/src/main/java/com/uid2/admin/vertx/service/CloudEncryptionKeyService.java
+++ b/src/main/java/com/uid2/admin/vertx/service/CloudEncryptionKeyService.java
@@ -3,44 +3,26 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.uid2.admin.auth.AdminAuthMiddleware;
-import com.uid2.admin.cloudencryption.CloudKeyRotationStrategy;
+import com.uid2.admin.cloudencryption.CloudEncryptionKeyManager;
import com.uid2.admin.model.CloudEncryptionKeyListResponse;
-import com.uid2.admin.model.CloudEncryptionKeySummary;
-import com.uid2.admin.store.writer.CloudEncryptionKeyStoreWriter;
import com.uid2.admin.vertx.Endpoints;
import com.uid2.shared.auth.Role;
-import com.uid2.shared.auth.RotatingOperatorKeyProvider;
-import com.uid2.shared.model.CloudEncryptionKey;
-import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider;
import com.uid2.shared.util.Mapper;
import io.vertx.core.http.HttpHeaders;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
public class CloudEncryptionKeyService implements IService {
private final AdminAuthMiddleware auth;
- private final RotatingCloudEncryptionKeyProvider keyProvider;
- private final RotatingOperatorKeyProvider operatorKeyProvider;
- private final CloudEncryptionKeyStoreWriter keyWriter;
- private final CloudKeyRotationStrategy rotationStrategy;
+ private final CloudEncryptionKeyManager keyManager;
private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance();
public CloudEncryptionKeyService(
AdminAuthMiddleware auth,
- RotatingCloudEncryptionKeyProvider keyProvider,
- CloudEncryptionKeyStoreWriter keyWriter,
- RotatingOperatorKeyProvider operatorKeyProvider,
- CloudKeyRotationStrategy rotationStrategy
+ CloudEncryptionKeyManager keyManager
) {
this.auth = auth;
- this.keyProvider = keyProvider;
- this.operatorKeyProvider = operatorKeyProvider;
- this.keyWriter = keyWriter;
- this.rotationStrategy = rotationStrategy;
+ this.keyManager = keyManager;
}
@Override
@@ -56,38 +38,16 @@ public void setupRoutes(Router router) {
private void handleRotate(RoutingContext rc) {
try {
- keyProvider.loadContent();
- operatorKeyProvider.loadContent(operatorKeyProvider.getMetadata());
- var operatorKeys = operatorKeyProvider.getAll();
- var existingKeys = keyProvider.getAll().values();
-
- var desiredKeys = rotationStrategy.computeDesiredKeys(existingKeys, operatorKeys);
- writeKeys(desiredKeys);
-
+ keyManager.rotateKeys();
rc.response().end();
} catch (Exception e) {
rc.fail(500, e);
}
}
- private void writeKeys(Set desiredKeys) throws Exception {
- var keysForWriting = desiredKeys.stream().collect(Collectors.toMap(
- CloudEncryptionKey::getId,
- Function.identity())
- );
- keyWriter.upload(keysForWriting, null);
- }
-
private void handleList(RoutingContext rc) {
try {
- keyProvider.loadContent();
-
- var keySummaries = keyProvider.getAll()
- .values()
- .stream()
- .map(CloudEncryptionKeySummary::fromFullKey)
- .toList();
- CloudEncryptionKeyListResponse response = new CloudEncryptionKeyListResponse(keySummaries);
+ var response = new CloudEncryptionKeyListResponse(keyManager.getKeySummaries());
respondWithJson(rc, response);
} catch (Exception e) {
rc.fail(500, e);
diff --git a/src/main/java/com/uid2/admin/vertx/service/OperatorKeyService.java b/src/main/java/com/uid2/admin/vertx/service/OperatorKeyService.java
index 0c42827d..45933957 100644
--- a/src/main/java/com/uid2/admin/vertx/service/OperatorKeyService.java
+++ b/src/main/java/com/uid2/admin/vertx/service/OperatorKeyService.java
@@ -47,8 +47,6 @@ public class OperatorKeyService implements IService {
private final KeyHasher keyHasher;
private final String operatorKeyPrefix;
private final CloudEncryptionKeyManager cloudEncryptionKeyManager;
- private final long cloudEncryptionKeyActivatesInSeconds;
- private final int cloudEncryptionKeyCountPerSite;
public OperatorKeyService(JsonObject config,
AdminAuthMiddleware auth,
@@ -69,8 +67,6 @@ public OperatorKeyService(JsonObject config,
this.cloudEncryptionKeyManager = cloudEncryptionKeyManager;
this.operatorKeyPrefix = config.getString("operator_key_prefix");
- this.cloudEncryptionKeyActivatesInSeconds = config.getLong("cloud_encryption_key_activates_in_seconds",0L);
- this.cloudEncryptionKeyCountPerSite = config.getInteger("cloud_encryption_key_count_per_site",0);
}
@Override
@@ -274,7 +270,8 @@ private void handleOperatorAdd(RoutingContext rc) {
// upload to storage
operatorKeyStoreWriter.upload(operators);
- cloudEncryptionKeyManager.generateKeysForOperators(Collections.singletonList(newOperator), cloudEncryptionKeyActivatesInSeconds, cloudEncryptionKeyCountPerSite);
+ // generate cloud encryption keys as needed
+ cloudEncryptionKeyManager.backfillKeys();
// respond with new key
rc.response().end(JSON_WRITER.writeValueAsString(new RevealedKey<>(newOperator, key)));
@@ -413,7 +410,7 @@ private void handleOperatorUpdate(RoutingContext rc) {
operatorKeyStoreWriter.upload(operators);
if (siteIdChanged) {
- cloudEncryptionKeyManager.generateKeysForOperators(Collections.singletonList(existingOperator), cloudEncryptionKeyActivatesInSeconds, cloudEncryptionKeyCountPerSite);
+ cloudEncryptionKeyManager.backfillKeys();
}
// return the updated client
diff --git a/src/test/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyDiffTest.java b/src/test/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyDiffTest.java
new file mode 100644
index 00000000..ff59cb27
--- /dev/null
+++ b/src/test/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyDiffTest.java
@@ -0,0 +1,54 @@
+package com.uid2.admin.cloudencryption;
+
+import com.uid2.shared.model.CloudEncryptionKey;
+import org.junit.jupiter.api.Test;
+
+import java.util.Set;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+class CloudEncryptionKeyDiffTest {
+ private final int siteId = 1;
+ private final String secret1 = "secret 1";
+ private final CloudEncryptionKey key1 = new CloudEncryptionKey(1, siteId, 0, 0, secret1);
+ private final CloudEncryptionKey key2 = new CloudEncryptionKey(2, siteId, 0, 0, secret1);
+ private final CloudEncryptionKey key3 = new CloudEncryptionKey(3, siteId, 0, 0, secret1);
+
+
+ @Test
+ void calculateDiff_noKeys() {
+ var expected = new CloudEncryptionKeyDiff(0, 0, 0, 0, 0);
+
+ var actual = CloudEncryptionKeyDiff.calculateDiff(Set.of(), Set.of());
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
+ void calculateDiff_noChange() {
+ var expected = new CloudEncryptionKeyDiff(2, 2, 0, 0, 2);
+
+ var actual = CloudEncryptionKeyDiff.calculateDiff(Set.of(key1, key2), Set.of(key1, key2));
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
+ void calculateDiff_keysCreated() {
+ var expected = new CloudEncryptionKeyDiff(2, 3, 1, 0, 2);
+
+ var actual = CloudEncryptionKeyDiff.calculateDiff(Set.of(key1, key2), Set.of(key1, key2, key3));
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
+ void calculateDiff_keysRemoved() {
+ var expected = new CloudEncryptionKeyDiff(3, 1, 0, 2, 1);
+
+ var actual = CloudEncryptionKeyDiff.calculateDiff(Set.of(key1, key2, key3), Set.of(key1));
+
+ assertThat(actual).isEqualTo(expected);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManagerTest.java b/src/test/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManagerTest.java
deleted file mode 100644
index 0ee89907..00000000
--- a/src/test/java/com/uid2/admin/cloudencryption/CloudEncryptionKeyManagerTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-package com.uid2.admin.cloudencryption;
-
-import com.uid2.admin.store.writer.CloudEncryptionKeyStoreWriter;
-import com.uid2.shared.auth.OperatorKey;
-import com.uid2.shared.model.CloudEncryptionKey;
-import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-import org.mockito.ArgumentCaptor;
-
-import java.util.*;
-
-import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.*;
-
-class CloudEncryptionKeyManagerTest {
- private RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider;
- private CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter;
- private CloudSecretGenerator keyGenerator;
- private CloudEncryptionKeyManager cloudEncryptionKeyManager;
-
- private final long keyActivateInterval = 3600; // 1 hour
- private final int keyCountPerSite = 3;
- private final int siteId = 1;
-
- @BeforeEach
- void setUp() {
- cloudEncryptionKeyProvider = mock(RotatingCloudEncryptionKeyProvider.class);
- cloudEncryptionKeyStoreWriter = mock(CloudEncryptionKeyStoreWriter.class);
- keyGenerator = mock(CloudSecretGenerator.class);
- cloudEncryptionKeyManager = new CloudEncryptionKeyManager(cloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, keyGenerator);
- }
-
- @Test
- void testGenerateCloudEncryptionKey() throws Exception {
- when(keyGenerator.generate()).thenReturn("randomKeyString");
-
- CloudEncryptionKey cloudEncryptionKey = cloudEncryptionKeyManager.generateCloudEncryptionKey(siteId, 1000L, 2000L);
-
- assertNotNull(cloudEncryptionKey);
- assertEquals(siteId, cloudEncryptionKey.getSiteId());
- assertEquals(1000L, cloudEncryptionKey.getActivates());
- assertEquals(2000L, cloudEncryptionKey.getCreated());
- assertEquals("randomKeyString", cloudEncryptionKey.getSecret());
- }
-
- @Test
- void testAddCloudEncryptionKeyToEmpty() throws Exception {
- CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, siteId, 1000L, 2000L, "randomKeyString");
-
- Map existingKeys = new HashMap<>();
- when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys);
-
- cloudEncryptionKeyManager.addCloudEncryptionKey(cloudEncryptionKey);
-
- ArgumentCaptor