11package com .uid2 .admin .cloudencryption ;
22
3+ import com .uid2 .admin .model .CloudEncryptionKeySummary ;
34import com .uid2 .admin .store .writer .CloudEncryptionKeyStoreWriter ;
45import com .uid2 .shared .auth .OperatorKey ;
6+ import com .uid2 .shared .auth .RotatingOperatorKeyProvider ;
57import com .uid2 .shared .model .CloudEncryptionKey ;
68import com .uid2 .shared .store .reader .RotatingCloudEncryptionKeyProvider ;
9+ import io .micrometer .core .instrument .Counter ;
10+ import io .micrometer .core .instrument .Metrics ;
711import org .slf4j .Logger ;
812import org .slf4j .LoggerFactory ;
913
10- import java .time .Instant ;
1114import java .util .*;
15+ import java .util .function .Function ;
16+ import java .util .stream .Collectors ;
1217
1318public class CloudEncryptionKeyManager {
19+ private final RotatingCloudEncryptionKeyProvider keyProvider ;
20+ private final RotatingOperatorKeyProvider operatorKeyProvider ;
21+ private final CloudEncryptionKeyStoreWriter keyWriter ;
22+ private final CloudKeyStatePlanner planner ;
23+ private Set <OperatorKey > operatorKeys ;
24+ private Set <CloudEncryptionKey > existingKeys ;
1425
1526 private static final Logger LOGGER = LoggerFactory .getLogger (CloudEncryptionKeyManager .class );
1627
17- private final RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider ;
18- private final CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter ;
19- private final CloudSecretGenerator secretGenerator ;
20-
2128 public CloudEncryptionKeyManager (
22- RotatingCloudEncryptionKeyProvider RotatingCloudEncryptionKeyProvider ,
23- CloudEncryptionKeyStoreWriter cloudEncryptionKeyStoreWriter ,
24- CloudSecretGenerator keyGenerator
25- ) {
26- this .RotatingCloudEncryptionKeyProvider = RotatingCloudEncryptionKeyProvider ;
27- this .cloudEncryptionKeyStoreWriter = cloudEncryptionKeyStoreWriter ;
28- this .secretGenerator = keyGenerator ;
29- }
30-
31- // Ensures there are `keyCountPerSite` sites for each site corresponding of operatorKeys. If there are less - create new ones.
32- // Give all new keys for each site `activationInterval` seconds between activations, starting now
33- public void generateKeysForOperators (
34- Collection <OperatorKey > operatorKeys ,
35- long activationInterval ,
36- int keyCountPerSite
37- ) throws Exception {
38- this .RotatingCloudEncryptionKeyProvider .loadContent ();
39-
40- if (operatorKeys == null || operatorKeys .isEmpty ()) {
41- throw new IllegalArgumentException ("Operator keys collection must not be null or empty" );
42- }
43- if (activationInterval <= 0 ) {
44- throw new IllegalArgumentException ("Key activate interval must be greater than zero" );
45- }
46- if (keyCountPerSite <= 0 ) {
47- throw new IllegalArgumentException ("Key count per site must be greater than zero" );
48- }
49-
50- for (Integer siteId : uniqueSiteIdsForOperators (operatorKeys )) {
51- ensureEnoughKeysForSite (activationInterval , keyCountPerSite , siteId );
52- }
29+ RotatingCloudEncryptionKeyProvider keyProvider ,
30+ CloudEncryptionKeyStoreWriter keyWriter ,
31+ RotatingOperatorKeyProvider operatorKeyProvider ,
32+ CloudKeyStatePlanner planner ) {
33+ this .keyProvider = keyProvider ;
34+ this .operatorKeyProvider = operatorKeyProvider ;
35+ this .keyWriter = keyWriter ;
36+ this .planner = planner ;
5337 }
5438
55- private void ensureEnoughKeysForSite (long activationInterval , int keyCountPerSite , Integer siteId ) throws Exception {
56- // Check if the site ID already exists in the S3 key provider and has fewer than the required number of keys
57- int currentKeyCount = countKeysForSite (siteId );
58- if (currentKeyCount >= keyCountPerSite ) {
59- LOGGER .info ("Site ID {} already has the required number of keys. Skipping key generation." , siteId );
60- return ;
61- }
62-
63- int keysToGenerate = keyCountPerSite - currentKeyCount ;
64- for (int i = 0 ; i < keysToGenerate ; i ++) {
65- addKey (activationInterval , siteId , i );
39+ // For any site that has an operator create a new key activating now
40+ // Keep up to 5 most recent old keys per site, delete the rest
41+ public void rotateKeys () throws Exception {
42+ boolean success = false ;
43+ try {
44+ refreshCloudData ();
45+ var desiredKeys = planner .planRotation (existingKeys , operatorKeys );
46+ writeKeys (desiredKeys );
47+ success = true ;
48+ var diff = CloudEncryptionKeyDiff .calculateDiff (existingKeys , desiredKeys );
49+ LOGGER .info ("Key rotation complete. Diff: {}" , diff );
50+ } catch (Exception e ) {
51+ success = false ;
52+ LOGGER .error ("Key rotation failed" , e );
53+ 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 );
6659 }
67- LOGGER .info ("Generated {} keys for site ID {}" , keysToGenerate , siteId );
68- }
69-
70- private void addKey (long keyActivateInterval , Integer siteId , int keyIndex ) throws Exception {
71- long created = Instant .now ().getEpochSecond ();
72- long activated = created + (keyIndex * keyActivateInterval );
73- CloudEncryptionKey cloudEncryptionKey = generateCloudEncryptionKey (siteId , activated , created );
74- addCloudEncryptionKey (cloudEncryptionKey );
7560 }
7661
77- private static Set <Integer > uniqueSiteIdsForOperators (Collection <OperatorKey > operatorKeys ) {
78- Set <Integer > uniqueSiteIds = new HashSet <>();
79- for (OperatorKey operatorKey : operatorKeys ) {
80- uniqueSiteIds .add (operatorKey .getSiteId ());
62+ // For any site that has an operator, if there are no keys, create a key activating now
63+ public void backfillKeys () throws Exception {
64+ try {
65+ refreshCloudData ();
66+ var desiredKeys = planner .planBackfill (existingKeys , operatorKeys );
67+ writeKeys (desiredKeys );
68+ var diff = CloudEncryptionKeyDiff .calculateDiff (existingKeys , desiredKeys );
69+ LOGGER .info ("Key backfill complete. Diff: {}" , diff );
70+ } catch (Exception e ) {
71+ LOGGER .error ("Key backfill failed" , e );
72+ throw e ;
8173 }
82- return uniqueSiteIds ;
83- }
84-
85- CloudEncryptionKey generateCloudEncryptionKey (int siteId , long activates , long created ) throws Exception {
86- int newKeyId = getNextKeyId ();
87- String secret = secretGenerator .generate ();
88- return new CloudEncryptionKey (newKeyId , siteId , activates , created , secret );
8974 }
9075
91- void addCloudEncryptionKey (CloudEncryptionKey cloudEncryptionKey ) throws Exception {
92- Map <Integer , CloudEncryptionKey > cloudEncryptionKeys = new HashMap <>(RotatingCloudEncryptionKeyProvider .getAll ());
93- cloudEncryptionKeys .put (cloudEncryptionKey .getId (), cloudEncryptionKey );
94- cloudEncryptionKeyStoreWriter .upload (cloudEncryptionKeys , null );
76+ public Set <CloudEncryptionKeySummary > getKeySummaries () throws Exception {
77+ refreshCloudData ();
78+ return existingKeys .stream ().map (CloudEncryptionKeySummary ::fromFullKey ).collect (Collectors .toSet ());
9579 }
9680
97- int getNextKeyId () {
98- Map < Integer , CloudEncryptionKey > cloudEncryptionKeys = RotatingCloudEncryptionKeyProvider . getAll ();
99- if ( cloudEncryptionKeys == null || cloudEncryptionKeys . isEmpty ()) {
100- return 1 ;
101- }
102- return cloudEncryptionKeys . keySet (). stream (). max ( Integer :: compareTo ). orElse ( 0 ) + 1 ;
81+ private void writeKeys ( Set < CloudEncryptionKey > desiredKeys ) throws Exception {
82+ var keysForWriting = desiredKeys . stream (). collect ( Collectors . toMap (
83+ CloudEncryptionKey :: getId ,
84+ Function . identity ())
85+ );
86+ keyWriter . upload ( keysForWriting , null ) ;
10387 }
10488
105- int countKeysForSite (int siteId ) {
106- Map <Integer , CloudEncryptionKey > allKeys = RotatingCloudEncryptionKeyProvider .getAll ();
107- return (int ) allKeys .values ().stream ().filter (key -> key .getSiteId () == siteId ).count ();
89+ private void refreshCloudData () throws Exception {
90+ keyProvider .loadContent ();
91+ operatorKeyProvider .loadContent (operatorKeyProvider .getMetadata ());
92+ operatorKeys = new HashSet <>(operatorKeyProvider .getAll ());
93+ existingKeys = new HashSet <>(keyProvider .getAll ().values ());
10894 }
10995}
0 commit comments