diff --git a/.gitignore b/.gitignore index 2d028bef..0c44dfc0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ target/* .idea/* .idea/ *.iml +.DS_Store \ No newline at end of file diff --git a/src/main/java/com/uid2/admin/Main.java b/src/main/java/com/uid2/admin/Main.java index 8af13488..1a324ae6 100644 --- a/src/main/java/com/uid2/admin/Main.java +++ b/src/main/java/com/uid2/admin/Main.java @@ -288,7 +288,11 @@ 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.generateKeysForOperators( + operatorKeyProvider.getAll(), + config.getLong("cloud_encryption_key_activates_in_seconds"), + config.getInteger("cloud_encryption_key_count_per_site") + ); RotatingCloudEncryptionKeyProvider.loadContent(); } diff --git a/src/main/java/com/uid2/admin/managers/CloudEncryptionKeyManager.java b/src/main/java/com/uid2/admin/managers/CloudEncryptionKeyManager.java index d351f22f..a37ea01a 100644 --- a/src/main/java/com/uid2/admin/managers/CloudEncryptionKeyManager.java +++ b/src/main/java/com/uid2/admin/managers/CloudEncryptionKeyManager.java @@ -7,6 +7,7 @@ import com.uid2.shared.store.reader.RotatingCloudEncryptionKeyProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -22,12 +23,70 @@ public class CloudEncryptionKeyManager { private final CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter; private final IKeyGenerator keyGenerator; - public CloudEncryptionKeyManager(RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider, CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter, IKeyGenerator keyGenerator) { + public CloudEncryptionKeyManager( + RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider, + CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter, + IKeyGenerator keyGenerator + ) { this.RotatingCloudEncryptionKeyProvider = RotatingCloudEncryptionKeyProvider; this.cloudEncryptionKeyStoreWriter = cloudEncryptionKeyStoreWriter; this.keyGenerator = 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); + } + } + + 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); + } + 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()); + } + return uniqueSiteIds; + } + CloudEncryptionKey generateCloudEncryptionKey(int siteId, long activates, long created) throws Exception { int newKeyId = getNextKeyId(); String secret = generateSecret(); @@ -53,8 +112,9 @@ int getNextKeyId() { return cloudEncryptionKeys.keySet().stream().max(Integer::compareTo).orElse(0) + 1; } - // Method to create and upload an S3 key that activates immediately for a specific site, for emergency rotation - public CloudEncryptionKey createAndAddImmediate3Key(int siteId) throws Exception { + // Used in test only + // Creates and uploads a CloudEncryptionKey that activates immediately for a specific sites, for emergency rotation + CloudEncryptionKey createAndAddImmediateCloudEncryptionKey(int siteId) throws Exception { int newKeyId = getNextKeyId(); long created = Instant.now().getEpochSecond(); CloudEncryptionKey newKey = new CloudEncryptionKey(newKeyId, siteId, created, created, generateSecret()); @@ -62,27 +122,32 @@ public CloudEncryptionKey createAndAddImmediate3Key(int siteId) throws Exception return newKey; } - public CloudEncryptionKey getCloudEncryptionKeyByKeyIdentifier(int keyIdentifier) { + // Used in test only + CloudEncryptionKey getCloudEncryptionKeyByKeyIdentifier(int keyIdentifier) { return RotatingCloudEncryptionKeyProvider.getAll().get(keyIdentifier); } - public Optional getCloudEncryptionKeyBySiteId(int siteId) { + // Used in test only + Optional getCloudEncryptionKeyBySiteId(int siteId) { return RotatingCloudEncryptionKeyProvider.getAll().values().stream() .filter(key -> key.getSiteId() == siteId) .findFirst(); } - public List getAllCloudEncryptionKeysBySiteId(int siteId) { + // Used in test only + List getAllCloudEncryptionKeysBySiteId(int siteId) { return RotatingCloudEncryptionKeyProvider.getAll().values().stream() .filter(key -> key.getSiteId() == siteId) .collect(Collectors.toList()); } - public Map getAllCloudEncryptionKeys() { + // Used in test only + Map getAllCloudEncryptionKeys() { return RotatingCloudEncryptionKeyProvider.getAll(); } - public boolean doesSiteHaveKeys(int siteId) { + // Used in test only + boolean doesSiteHaveKeys(int siteId) { Map allKeys = RotatingCloudEncryptionKeyProvider.getAll(); if (allKeys == null) { return false; @@ -94,41 +159,4 @@ int countKeysForSite(int siteId) { Map allKeys = RotatingCloudEncryptionKeyProvider.getAll(); return (int) allKeys.values().stream().filter(key -> key.getSiteId() == siteId).count(); } - - public void generateKeysForOperators(Collection operatorKeys, long keyActivateInterval, 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 (keyActivateInterval <= 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"); - } - - // Extract all the unique site IDs from input operator keys collection - Set uniqueSiteIds = new HashSet<>(); - for (OperatorKey operatorKey : operatorKeys) { - uniqueSiteIds.add(operatorKey.getSiteId()); - } - - for (Integer siteId : uniqueSiteIds) { - // 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) { - int keysToGenerate = keyCountPerSite - currentKeyCount; - for (int i = 0; i < keysToGenerate; i++) { - long created = Instant.now().getEpochSecond(); - long activated = created + (i * keyActivateInterval); - CloudEncryptionKey cloudEncryptionKey = generateCloudEncryptionKey(siteId, activated, created); - addCloudEncryptionKey(cloudEncryptionKey); - } - LOGGER.info("Generated " + keysToGenerate + " keys for site ID " + siteId); - } else { - LOGGER.info("Site ID " + siteId + " already has the required number of keys. Skipping key generation."); - } - } - } } \ No newline at end of file diff --git a/src/test/java/com/uid2/admin/managers/CloudEncryptionKeyManagerTest.java b/src/test/java/com/uid2/admin/managers/CloudEncryptionKeyManagerTest.java index 405f5464..8c687612 100644 --- a/src/test/java/com/uid2/admin/managers/CloudEncryptionKeyManagerTest.java +++ b/src/test/java/com/uid2/admin/managers/CloudEncryptionKeyManagerTest.java @@ -16,28 +16,31 @@ import static org.mockito.Mockito.*; class CloudEncryptionKeyManagerTest { - private RotatingCloudEncryptionKeyProvider cloudEncryptionKeyProvider; private CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter; private IKeyGenerator 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(IKeyGenerator.class); - cloudEncryptionKeyManager = new CloudEncryptionKeyManager(cloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter,keyGenerator); + cloudEncryptionKeyManager = new CloudEncryptionKeyManager(cloudEncryptionKeyProvider, cloudEncryptionKeyStoreWriter, keyGenerator); } @Test void testGenerateCloudEncryptionKey() throws Exception { when(keyGenerator.generateRandomKeyString(32)).thenReturn("randomKeyString"); - CloudEncryptionKey cloudEncryptionKey = cloudEncryptionKeyManager.generateCloudEncryptionKey(1, 1000L, 2000L); + CloudEncryptionKey cloudEncryptionKey = cloudEncryptionKeyManager.generateCloudEncryptionKey(siteId, 1000L, 2000L); assertNotNull(cloudEncryptionKey); - assertEquals(1, cloudEncryptionKey.getSiteId()); + assertEquals(siteId, cloudEncryptionKey.getSiteId()); assertEquals(1000L, cloudEncryptionKey.getActivates()); assertEquals(2000L, cloudEncryptionKey.getCreated()); assertEquals("randomKeyString", cloudEncryptionKey.getSecret()); @@ -45,28 +48,28 @@ void testGenerateCloudEncryptionKey() throws Exception { @Test void testAddCloudEncryptionKeyToEmpty() throws Exception { - CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, 1, 1000L, 2000L, "randomKeyString"); + CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, siteId, 1000L, 2000L, "randomKeyString"); Map existingKeys = new HashMap<>(); when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys); - cloudEncryptionKeyManager.addCloudEncryptionKey( cloudEncryptionKey); + cloudEncryptionKeyManager.addCloudEncryptionKey(cloudEncryptionKey); ArgumentCaptor captor = ArgumentCaptor.forClass(Map.class); verify(cloudEncryptionKeyStoreWriter).upload(captor.capture(), isNull()); Map capturedKeys = captor.getValue(); assertEquals(1, capturedKeys.size()); - assertEquals( cloudEncryptionKey, capturedKeys.get(1)); + assertEquals(cloudEncryptionKey, capturedKeys.get(1)); } @Test void testAddCloudEncryptionKeyToExisting() throws Exception { - CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(3, 1, 1000L, 2000L, "randomKeyString"); + CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(3, siteId, 1000L, 2000L, "randomKeyString"); Map existingKeys = new HashMap<>(); - CloudEncryptionKey existingKey1 = new CloudEncryptionKey(1, 1, 500L, 1500L, "existingSecret1"); - CloudEncryptionKey existingKey2 = new CloudEncryptionKey(2, 1, 600L, 1600L, "existingSecret2"); + CloudEncryptionKey existingKey1 = new CloudEncryptionKey(1, siteId, 500L, 1500L, "existingSecret1"); + CloudEncryptionKey existingKey2 = new CloudEncryptionKey(2, siteId, 600L, 1600L, "existingSecret2"); existingKeys.put(1, existingKey1); existingKeys.put(2, existingKey2); @@ -88,7 +91,7 @@ void testAddCloudEncryptionKeyToExisting() throws Exception { @Test void testGetNextKeyId() { Map existingKeys = new HashMap<>(); - existingKeys.put(1, new CloudEncryptionKey(1, 1, 500L, 1500L, "existingSecret1")); + existingKeys.put(1, new CloudEncryptionKey(1, siteId, 500L, 1500L, "existingSecret1")); when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys); int nextKeyId = cloudEncryptionKeyManager.getNextKeyId(); @@ -98,7 +101,7 @@ void testGetNextKeyId() { @Test void testGetCloudEncryptionKey() { - CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, 1, 500L, 1500L, "existingSecret1"); + CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, siteId, 500L, 1500L, "existingSecret1"); Map existingKeys = new HashMap<>(); existingKeys.put(1, cloudEncryptionKey); when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys); @@ -111,8 +114,8 @@ void testGetCloudEncryptionKey() { @Test void testGetAllCloudEncryptionKeys() { Map existingKeys = new HashMap<>(); - CloudEncryptionKey existingKey1 = new CloudEncryptionKey(1, 1, 500L, 1500L, "existingSecret1"); - CloudEncryptionKey existingKey2 = new CloudEncryptionKey(2, 1, 600L, 1600L, "existingSecret2"); + CloudEncryptionKey existingKey1 = new CloudEncryptionKey(1, siteId, 500L, 1500L, "existingSecret1"); + CloudEncryptionKey existingKey2 = new CloudEncryptionKey(2, siteId, 600L, 1600L, "existingSecret2"); existingKeys.put(1, existingKey1); existingKeys.put(2, existingKey2); @@ -125,7 +128,7 @@ void testGetAllCloudEncryptionKeys() { @Test void testAddCloudEncryptionKey() throws Exception { - CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, 1, 1000L, 2000L, "randomKeyString"); + CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(1, siteId, 1000L, 2000L, "randomKeyString"); Map existingKeys = new HashMap<>(); when(cloudEncryptionKeyProvider.getAll()).thenReturn(existingKeys); @@ -178,7 +181,7 @@ void testCreateAndAddImmediateCloudEncryptionKey() throws Exception { when(cloudEncryptionKeyProvider.getAll()).thenReturn(new HashMap<>()); when(keyGenerator.generateRandomKeyString(32)).thenReturn("generatedSecret"); - CloudEncryptionKey newKey = cloudEncryptionKeyManager.createAndAddImmediate3Key(100); + CloudEncryptionKey newKey = cloudEncryptionKeyManager.createAndAddImmediateCloudEncryptionKey(100); assertNotNull(newKey); assertEquals(100, newKey.getSiteId()); @@ -189,7 +192,6 @@ void testCreateAndAddImmediateCloudEncryptionKey() throws Exception { @Test public void testDoesSiteHaveKeys_SiteHasKeys() { - int siteId = 1; CloudEncryptionKey cloudEncryptionKey = new CloudEncryptionKey(siteId, siteId, 0L, 0L, "key"); Map allKeys = new HashMap<>(); allKeys.put(1, cloudEncryptionKey); @@ -199,9 +201,9 @@ public void testDoesSiteHaveKeys_SiteHasKeys() { boolean result = cloudEncryptionKeyManager.doesSiteHaveKeys(siteId); assertTrue(result); } + @Test public void testDoesSiteHaveKeys_SiteDoesNotHaveKeys() { - int siteId = 1; Map allKeys = new HashMap<>(); when(cloudEncryptionKeyProvider.getAll()).thenReturn(allKeys); @@ -212,8 +214,6 @@ public void testDoesSiteHaveKeys_SiteDoesNotHaveKeys() { @Test public void testDoesSiteHaveKeys_AllKeysNull() { - int siteId = 1; - when(cloudEncryptionKeyProvider.getAll()).thenReturn(null); boolean result = cloudEncryptionKeyManager.doesSiteHaveKeys(siteId); @@ -237,7 +237,6 @@ public void testDoesSiteHaveKeys_MultipleKeysDifferentSiteIds() { @Test public void testDoesSiteHaveKeys_SameSiteIdMultipleKeys() { - int siteId = 1; CloudEncryptionKey cloudEncryptionKey1 = new CloudEncryptionKey(siteId, siteId, 0L, 0L, "key1"); CloudEncryptionKey cloudEncryptionKey2 = new CloudEncryptionKey(siteId, siteId, 0L, 0L, "key2"); Map allKeys = new HashMap<>(); @@ -299,8 +298,6 @@ void testGenerateKeysForOperators() throws Exception { createOperatorKey("hash2", 100), createOperatorKey("hash3", 200) ); - long keyActivateInterval = 3600; // 1 hour - int keyCountPerSite = 3; Map existingKeys = new HashMap<>(); existingKeys.put(1, new CloudEncryptionKey(1, 100, 1000L, 900L, "existingKey1")); @@ -322,8 +319,6 @@ void testGenerateKeysForOperators_NoNewKeysNeeded() throws Exception { Collection operatorKeys = Collections.singletonList( createOperatorKey("hash1", 100) ); - long keyActivateInterval = 3600; - int keyCountPerSite = 3; Map existingKeys = new HashMap<>(); existingKeys.put(1, new CloudEncryptionKey(1, 100, 1000L, 900L, "existingKey1")); @@ -339,8 +334,6 @@ void testGenerateKeysForOperators_NoNewKeysNeeded() throws Exception { @Test void testGenerateKeysForOperators_EmptyOperatorKeys() { Collection operatorKeys = Collections.emptyList(); - long keyActivateInterval = 3600; - int keyCountPerSite = 3; assertThrows(IllegalArgumentException.class, () -> cloudEncryptionKeyManager.generateKeysForOperators(operatorKeys, keyActivateInterval, keyCountPerSite) @@ -353,7 +346,6 @@ void testGenerateKeysForOperators_InvalidKeyActivateInterval() { createOperatorKey("hash1", 100) ); long keyActivateInterval = 0; - int keyCountPerSite = 3; assertThrows(IllegalArgumentException.class, () -> cloudEncryptionKeyManager.generateKeysForOperators(operatorKeys, keyActivateInterval, keyCountPerSite) @@ -365,7 +357,6 @@ void testGenerateKeysForOperators_InvalidKeyCountPerSite() { Collection operatorKeys = Collections.singletonList( createOperatorKey("hash1", 100) ); - long keyActivateInterval = 3600; int keyCountPerSite = 0; assertThrows(IllegalArgumentException.class, () -> @@ -380,8 +371,6 @@ void testGenerateKeysForOperators_MultipleSitesWithVaryingExistingKeys() throws createOperatorKey("hash2", 200), createOperatorKey("hash3", 300) ); - long keyActivateInterval = 3600; - int keyCountPerSite = 3; Map existingKeys = new HashMap<>(); existingKeys.put(1, new CloudEncryptionKey(1, 100, 1000L, 900L, "existingKey1"));