Skip to content

Commit 0014e24

Browse files
committed
Monitoring
1 parent c94689b commit 0014e24

File tree

6 files changed

+161
-22
lines changed

6 files changed

+161
-22
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.uid2.admin.cloudencryption;
2+
3+
import com.uid2.shared.model.CloudEncryptionKey;
4+
5+
import java.text.MessageFormat;
6+
import java.util.HashSet;
7+
import java.util.Set;
8+
9+
public record CloudEncryptionKeyDiff(
10+
int before,
11+
int after,
12+
int created,
13+
int removed,
14+
int unchanged
15+
) {
16+
public static CloudEncryptionKeyDiff calculateDiff(Set<CloudEncryptionKey> keysBefore, Set<CloudEncryptionKey> keysAfter) {
17+
var before = keysBefore.size();
18+
var after = keysAfter.size();
19+
20+
var intersection = new HashSet<>(keysBefore);
21+
intersection.retainAll(keysAfter);
22+
int unchanged = intersection.size();
23+
24+
var onlyInLeft = new HashSet<>(keysBefore);
25+
onlyInLeft.removeAll(keysAfter);
26+
int removed = onlyInLeft.size();
27+
28+
var onlyInRight = new HashSet<>(keysAfter);
29+
onlyInRight.removeAll(keysBefore);
30+
int created = onlyInRight.size();
31+
32+
return new CloudEncryptionKeyDiff(before, after, created, removed, unchanged);
33+
}
34+
35+
@Override
36+
public String toString() {
37+
return MessageFormat.format(
38+
"before={0}, after={1}, created={2}, removed={3}, unchanged={4}",
39+
before,
40+
after,
41+
created,
42+
removed,
43+
unchanged
44+
);
45+
}
46+
}

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

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
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.Gauge;
11+
import io.micrometer.core.instrument.Metrics;
912
import org.slf4j.Logger;
1013
import org.slf4j.LoggerFactory;
1114

@@ -18,8 +21,10 @@ public class CloudEncryptionKeyManager {
1821
private final RotatingOperatorKeyProvider operatorKeyProvider;
1922
private final CloudEncryptionKeyStoreWriter keyWriter;
2023
private final CloudKeyStatePlanner planner;
21-
private Collection<OperatorKey> operatorKeys;
22-
private Collection<CloudEncryptionKey> existingKeys;
24+
private Set<OperatorKey> operatorKeys;
25+
private Set<CloudEncryptionKey> existingKeys;
26+
27+
private static final Logger logger = LoggerFactory.getLogger(CloudEncryptionKeyManager.class);
2328

2429
public CloudEncryptionKeyManager(
2530
RotatingCloudEncryptionKeyProvider keyProvider,
@@ -32,21 +37,41 @@ public CloudEncryptionKeyManager(
3237
this.planner = planner;
3338
}
3439

35-
private static final Logger logger = LoggerFactory.getLogger(CloudEncryptionKeyManager.class);
36-
3740
// For any site that has an operator create a new key activating now
3841
// Keep up to 5 most recent old keys per site, delete the rest
3942
public void rotateKeys() throws Exception {
40-
refreshCloudData();
41-
var desiredKeys = planner.planRotation(existingKeys, operatorKeys);
42-
writeKeys(desiredKeys);
43+
boolean success = false;
44+
try {
45+
refreshCloudData();
46+
var desiredKeys = planner.planRotation(existingKeys, operatorKeys);
47+
writeKeys(desiredKeys);
48+
success = true;
49+
var diff = CloudEncryptionKeyDiff.calculateDiff(existingKeys, desiredKeys);
50+
logger.info("Key rotation complete. Diff: {}", diff);
51+
} catch (Exception e) {
52+
success = false;
53+
logger.error("Key rotation failed", e);
54+
throw e;
55+
} finally {
56+
Counter.builder("uid2.cloud_encryption_key_manager.rotations")
57+
.tag("success", Boolean.toString(success))
58+
.description("The number of times rotations have happened")
59+
.register(Metrics.globalRegistry);
60+
}
4361
}
4462

4563
// For any site that has an operator, if there are no keys, create a key activating now
4664
public void backfillKeys() throws Exception {
47-
refreshCloudData();
48-
var desiredKeys = planner.planBackfill(existingKeys, operatorKeys);
49-
writeKeys(desiredKeys);
65+
try {
66+
refreshCloudData();
67+
var desiredKeys = planner.planBackfill(existingKeys, operatorKeys);
68+
writeKeys(desiredKeys);
69+
var diff = CloudEncryptionKeyDiff.calculateDiff(existingKeys, desiredKeys);
70+
logger.info("Key backfill complete. Diff: {}", diff);
71+
} catch (Exception e) {
72+
logger.error("Key backfill failed", e);
73+
throw e;
74+
}
5075
}
5176

5277
public List<CloudEncryptionKeySummary> getKeySummaries() throws Exception {
@@ -65,7 +90,7 @@ private void writeKeys(Set<CloudEncryptionKey> desiredKeys) throws Exception {
6590
private void refreshCloudData() throws Exception {
6691
keyProvider.loadContent();
6792
operatorKeyProvider.loadContent(operatorKeyProvider.getMetadata());
68-
operatorKeys = operatorKeyProvider.getAll();
69-
existingKeys = keyProvider.getAll().values();
93+
operatorKeys = new HashSet<>(operatorKeyProvider.getAll());
94+
existingKeys = new HashSet<>(keyProvider.getAll().values());
7095
}
7196
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import com.uid2.shared.auth.OperatorKey;
66
import com.uid2.shared.model.CloudEncryptionKey;
77

8+
import java.text.MessageFormat;
89
import java.util.Collection;
10+
import java.util.HashSet;
911
import java.util.Map;
1012
import java.util.Set;
1113
import java.util.stream.Collectors;
@@ -27,8 +29,8 @@ public CloudKeyStatePlanner(
2729
}
2830

2931
public Set<CloudEncryptionKey> planRotation(
30-
Collection<CloudEncryptionKey> existingKeys,
31-
Collection<OperatorKey> operatorKeys
32+
Set<CloudEncryptionKey> existingKeys,
33+
Set<OperatorKey> operatorKeys
3234
) {
3335
var keyGenerator = new CloudEncryptionKeyGenerator(clock, secretGenerator, existingKeys);
3436
Map<Integer, Set<CloudEncryptionKey>> existingKeysBySite = existingKeys
@@ -41,8 +43,8 @@ public Set<CloudEncryptionKey> planRotation(
4143
}
4244

4345
public Set<CloudEncryptionKey> planBackfill(
44-
Collection<CloudEncryptionKey> existingKeys,
45-
Collection<OperatorKey> operatorKeys
46+
Set<CloudEncryptionKey> existingKeys,
47+
Set<OperatorKey> operatorKeys
4648
) {
4749
var keyGenerator = new CloudEncryptionKeyGenerator(clock, secretGenerator, existingKeys);
4850
var siteIdsWithKeys = existingKeys.stream().map(CloudEncryptionKey::getSiteId).collect(Collectors.toSet());
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.uid2.admin.cloudencryption;
2+
3+
import com.uid2.shared.model.CloudEncryptionKey;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.Set;
7+
8+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
9+
import static org.junit.jupiter.api.Assertions.*;
10+
11+
class CloudEncryptionKeyDiffTest {
12+
private final int siteId = 1;
13+
private final String secret1 = "secret 1";
14+
private final CloudEncryptionKey key1 = new CloudEncryptionKey(1, siteId, 0, 0, secret1);
15+
private final CloudEncryptionKey key2 = new CloudEncryptionKey(2, siteId, 0, 0, secret1);
16+
private final CloudEncryptionKey key3 = new CloudEncryptionKey(3, siteId, 0, 0, secret1);
17+
18+
19+
@Test
20+
void calculateDiff_noKeys() {
21+
var expected = new CloudEncryptionKeyDiff(0, 0, 0, 0, 0);
22+
23+
var actual = CloudEncryptionKeyDiff.calculateDiff(Set.of(), Set.of());
24+
25+
assertThat(actual).isEqualTo(expected);
26+
}
27+
28+
@Test
29+
void calculateDiff_noChange() {
30+
var expected = new CloudEncryptionKeyDiff(2, 2, 0, 0, 2);
31+
32+
var actual = CloudEncryptionKeyDiff.calculateDiff(Set.of(key1, key2), Set.of(key1, key2));
33+
34+
assertThat(actual).isEqualTo(expected);
35+
}
36+
37+
@Test
38+
void calculateDiff_keysCreated() {
39+
var expected = new CloudEncryptionKeyDiff(2, 3, 1, 0, 2);
40+
41+
var actual = CloudEncryptionKeyDiff.calculateDiff(Set.of(key1, key2), Set.of(key1, key2, key3));
42+
43+
assertThat(actual).isEqualTo(expected);
44+
}
45+
46+
@Test
47+
void calculateDiff_keysRemoved() {
48+
var expected = new CloudEncryptionKeyDiff(3, 1, 0, 2, 1);
49+
50+
var actual = CloudEncryptionKeyDiff.calculateDiff(Set.of(key1, key2, key3), Set.of(key1));
51+
52+
assertThat(actual).isEqualTo(expected);
53+
}
54+
}

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ void setUp() {
3636
@Test
3737
void planBackfill_doesNotRemoveKeysWhenOperatorsForKeyMissing() {
3838
// We do cleanup in rotation, this job is supposed to be extremely safe and won't delete anything
39-
var existingCloudKeys = List.of(key1);
40-
var operatorKeys = List.<OperatorKey>of();
39+
var existingCloudKeys = Set.of(key1);
40+
var operatorKeys = Set.<OperatorKey>of();
4141
var expected = Set.of(key1);
4242

4343
var actual = planner.planBackfill(existingCloudKeys, operatorKeys);
@@ -47,8 +47,8 @@ void planBackfill_doesNotRemoveKeysWhenOperatorsForKeyMissing() {
4747

4848
@Test
4949
void planBackfill_createsNoNewKeysForOperatorsWithExistingKeys() {
50-
var existingCloudKeys = List.of(key1);
51-
var operatorKeys = List.of(operatorKey1);
50+
var existingCloudKeys = Set.of(key1);
51+
var operatorKeys = Set.of(operatorKey1);
5252
var expected = Set.of(key1);
5353

5454
var actual = planner.planBackfill(existingCloudKeys, operatorKeys);
@@ -58,8 +58,8 @@ void planBackfill_createsNoNewKeysForOperatorsWithExistingKeys() {
5858

5959
@Test
6060
void planBackfill_createsNewKeysForOperatorsWithNoKeys() {
61-
var existingCloudKeys = List.<CloudEncryptionKey>of();
62-
var operatorKeys = List.of(operatorKey1);
61+
var existingCloudKeys = Set.<CloudEncryptionKey>of();
62+
var operatorKeys = Set.of(operatorKey1);
6363
var expected = Set.of(key1);
6464

6565
var actual = planner.planBackfill(existingCloudKeys, operatorKeys);

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,18 @@ key2Id, new CloudEncryptionKey(key2Id, siteId1, now, now, secret2)
232232
});
233233
}
234234

235+
@Test
236+
public void testRotate_handleExceptions(Vertx vertx, VertxTestContext testContext) {
237+
fakeAuth(Role.MAINTAINER);
238+
setOperatorKeys(operator1);
239+
when(clock.getEpochSecond()).thenThrow(new RuntimeException("oops"));
240+
241+
post(vertx, testContext, Endpoints.CLOUD_ENCRYPTION_KEY_ROTATE, null, rotateResponse -> {
242+
assertEquals(500, rotateResponse.statusCode());
243+
testContext.completeNow();
244+
});
245+
}
246+
235247
private static CloudEncryptionKeyListResponse parseKeyListResponse(HttpResponse<Buffer> response) throws JsonProcessingException {
236248
return OBJECT_MAPPER.readValue(response.bodyAsString(), new TypeReference<>() {
237249
});

0 commit comments

Comments
 (0)