|
1 | 1 | package com.uid2.admin.salt; |
2 | 2 |
|
| 3 | +import com.uid2.admin.AdminConst; |
3 | 4 | import com.uid2.shared.model.SaltEntry; |
4 | 5 | import com.uid2.shared.secret.IKeyGenerator; |
5 | 6 |
|
6 | 7 | import com.uid2.shared.store.salt.ISaltProvider.ISaltSnapshot; |
7 | 8 | import com.uid2.shared.store.salt.RotatingSaltProvider.SaltSnapshot; |
| 9 | +import io.vertx.core.json.JsonObject; |
8 | 10 | import lombok.Getter; |
9 | 11 | import org.slf4j.Logger; |
10 | 12 | import org.slf4j.LoggerFactory; |
|
17 | 19 | public class SaltRotation { |
18 | 20 | private static final long THIRTY_DAYS_IN_MS = Duration.ofDays(30).toMillis(); |
19 | 21 | private static final double MAX_SALT_PERCENTAGE = 0.8; |
| 22 | + private final boolean ENABLE_V4_RAW_UID; |
20 | 23 |
|
21 | 24 | private final IKeyGenerator keyGenerator; |
| 25 | + |
22 | 26 | private static final Logger LOGGER = LoggerFactory.getLogger(SaltRotation.class); |
23 | 27 |
|
24 | | - public SaltRotation(IKeyGenerator keyGenerator) { |
| 28 | + public SaltRotation(IKeyGenerator keyGenerator, JsonObject config) { |
25 | 29 | this.keyGenerator = keyGenerator; |
| 30 | + this.ENABLE_V4_RAW_UID = config.getBoolean(AdminConst.ENABLE_V4_RAW_UID, false); |
26 | 31 | } |
27 | 32 |
|
28 | 33 | public Result rotateSalts( |
@@ -57,6 +62,7 @@ public Result rotateSalts( |
57 | 62 | logSaltAges("refreshable-salts", targetDate, refreshableSalts); |
58 | 63 | logSaltAges("rotated-salts", targetDate, saltsToRotate); |
59 | 64 | logSaltAges("total-salts", targetDate, Arrays.asList(postRotationSalts)); |
| 65 | + logBucketFormatCount(targetDate, postRotationSalts); |
60 | 66 |
|
61 | 67 | var nextSnapshot = new SaltSnapshot( |
62 | 68 | nextEffective, |
@@ -99,45 +105,85 @@ private boolean isRefreshable(TargetDate targetDate, SaltEntry salt) { |
99 | 105 | } |
100 | 106 |
|
101 | 107 | private SaltEntry[] rotateSalts(SaltEntry[] oldSalts, List<SaltEntry> saltsToRotate, TargetDate targetDate) throws Exception { |
| 108 | + var keyIdGenerator = new KeyIdGenerator(oldSalts); |
102 | 109 | var saltIdsToRotate = saltsToRotate.stream().map(SaltEntry::id).collect(Collectors.toSet()); |
103 | 110 |
|
104 | 111 | var updatedSalts = new SaltEntry[oldSalts.length]; |
105 | 112 | for (int i = 0; i < oldSalts.length; i++) { |
106 | 113 | var shouldRotate = saltIdsToRotate.contains(oldSalts[i].id()); |
107 | | - updatedSalts[i] = updateSalt(oldSalts[i], targetDate, shouldRotate); |
| 114 | + updatedSalts[i] = updateSalt(oldSalts[i], targetDate, shouldRotate, keyIdGenerator); |
108 | 115 | } |
109 | 116 | return updatedSalts; |
110 | 117 | } |
111 | 118 |
|
112 | | - private SaltEntry updateSalt(SaltEntry oldSalt, TargetDate targetDate, boolean shouldRotate) throws Exception { |
113 | | - var currentSalt = shouldRotate ? this.keyGenerator.generateRandomKeyString(32) : oldSalt.currentSalt(); |
114 | | - var lastUpdated = shouldRotate ? targetDate.asEpochMs() : oldSalt.lastUpdated(); |
115 | | - var refreshFrom = calculateRefreshFrom(oldSalt, targetDate); |
116 | | - var previousSalt = calculatePreviousSalt(oldSalt, shouldRotate, targetDate); |
| 119 | + private SaltEntry updateSalt(SaltEntry oldBucket, TargetDate targetDate, boolean shouldRotate, KeyIdGenerator keyIdGenerator) throws Exception { |
| 120 | + var lastUpdated = shouldRotate ? targetDate.asEpochMs() : oldBucket.lastUpdated(); |
| 121 | + var refreshFrom = calculateRefreshFrom(oldBucket, targetDate); |
| 122 | + var currentSalt = calculateCurrentSalt(oldBucket, shouldRotate); |
| 123 | + var previousSalt = calculatePreviousSalt(oldBucket, shouldRotate, targetDate); |
| 124 | + var currentKeySalt = calculateCurrentKeySalt(oldBucket, shouldRotate, keyIdGenerator); |
| 125 | + var previousKeySalt = calculatePreviousKeySalt(oldBucket,shouldRotate, targetDate); |
117 | 126 |
|
118 | 127 | return new SaltEntry( |
119 | | - oldSalt.id(), |
120 | | - oldSalt.hashedId(), |
| 128 | + oldBucket.id(), |
| 129 | + oldBucket.hashedId(), |
121 | 130 | lastUpdated, |
122 | 131 | currentSalt, |
123 | 132 | refreshFrom, |
124 | 133 | previousSalt, |
125 | | - null, |
126 | | - null |
| 134 | + currentKeySalt, |
| 135 | + previousKeySalt |
127 | 136 | ); |
128 | 137 | } |
129 | 138 |
|
130 | | - private long calculateRefreshFrom(SaltEntry salt, TargetDate targetDate) { |
131 | | - long multiplier = targetDate.saltAgeInDays(salt) / 30 + 1; |
132 | | - return Instant.ofEpochMilli(salt.lastUpdated()).truncatedTo(ChronoUnit.DAYS).toEpochMilli() + (multiplier * THIRTY_DAYS_IN_MS); |
| 139 | + private long calculateRefreshFrom(SaltEntry bucket, TargetDate targetDate) { |
| 140 | + long multiplier = targetDate.saltAgeInDays(bucket) / 30 + 1; |
| 141 | + return Instant.ofEpochMilli(bucket.lastUpdated()).truncatedTo(ChronoUnit.DAYS).toEpochMilli() + (multiplier * THIRTY_DAYS_IN_MS); |
| 142 | + } |
| 143 | + |
| 144 | + private String calculateCurrentSalt(SaltEntry bucket, boolean shouldRotate) throws Exception { |
| 145 | + if (shouldRotate) { |
| 146 | + if (ENABLE_V4_RAW_UID) { |
| 147 | + return null; |
| 148 | + } |
| 149 | + else { |
| 150 | + return this.keyGenerator.generateRandomKeyString(32); |
| 151 | + } |
| 152 | + } |
| 153 | + return bucket.currentSalt(); |
133 | 154 | } |
134 | 155 |
|
135 | | - private String calculatePreviousSalt(SaltEntry salt, boolean shouldRotate, TargetDate targetDate) { |
| 156 | + private String calculatePreviousSalt(SaltEntry bucket, boolean shouldRotate, TargetDate targetDate) { |
136 | 157 | if (shouldRotate) { |
137 | | - return salt.currentSalt(); |
| 158 | + return bucket.currentSalt(); |
138 | 159 | } |
139 | | - if (targetDate.saltAgeInDays(salt) < 90) { |
140 | | - return salt.previousSalt(); |
| 160 | + if (targetDate.saltAgeInDays(bucket) < 90) { |
| 161 | + return bucket.previousSalt(); |
| 162 | + } |
| 163 | + return null; |
| 164 | + } |
| 165 | + |
| 166 | + private SaltEntry.KeyMaterial calculateCurrentKeySalt(SaltEntry bucket, boolean shouldRotate, KeyIdGenerator keyIdGenerator) throws Exception { |
| 167 | + if (shouldRotate) { |
| 168 | + if (ENABLE_V4_RAW_UID) { |
| 169 | + return new SaltEntry.KeyMaterial( |
| 170 | + keyIdGenerator.getNextKeyId(), |
| 171 | + this.keyGenerator.generateRandomKeyString(32), |
| 172 | + this.keyGenerator.generateRandomKeyString(32) |
| 173 | + ); |
| 174 | + } else { |
| 175 | + return null; |
| 176 | + } |
| 177 | + } |
| 178 | + return bucket.currentKeySalt(); |
| 179 | + } |
| 180 | + |
| 181 | + private SaltEntry.KeyMaterial calculatePreviousKeySalt(SaltEntry bucket, boolean shouldRotate, TargetDate targetDate) { |
| 182 | + if (shouldRotate) { |
| 183 | + return bucket.currentKeySalt(); |
| 184 | + } |
| 185 | + if (targetDate.saltAgeInDays(bucket) < 90) { |
| 186 | + return bucket.previousKeySalt(); |
141 | 187 | } |
142 | 188 | return null; |
143 | 189 | } |
@@ -207,6 +253,24 @@ private void logSaltAges(String saltCountType, TargetDate targetDate, Collection |
207 | 253 | } |
208 | 254 | } |
209 | 255 |
|
| 256 | + |
| 257 | + /** Logging to monitor migration of buckets from salts (old format - v2/v3) to encryption keys (new format - v4) **/ |
| 258 | + private void logBucketFormatCount(TargetDate targetDate, SaltEntry[] postRotationBuckets) { |
| 259 | + int totalKeys = 0, totalSalts = 0, totalPreviousKeys = 0, totalPreviousSalts = 0; |
| 260 | + |
| 261 | + for (SaltEntry bucket : postRotationBuckets) { |
| 262 | + if (bucket.currentKeySalt() != null) totalKeys++; |
| 263 | + if (bucket.currentSalt() != null) totalSalts++; |
| 264 | + if (bucket.previousKeySalt() != null) totalPreviousKeys++; |
| 265 | + if (bucket.previousSalt() != null) totalPreviousSalts++; |
| 266 | + } |
| 267 | + |
| 268 | + LOGGER.info("UID bucket format: target_date={} bucket_format={} bucket_count={}", targetDate, "total-current-key-buckets", totalKeys); |
| 269 | + LOGGER.info("UID bucket format: target_date={} bucket_format={} bucket_count={}", targetDate, "total-current-salt-buckets", totalSalts); |
| 270 | + LOGGER.info("UID bucket format: target_date={} bucket_format={} bucket_count={}", targetDate, "total-previous-key-buckets", totalPreviousKeys); |
| 271 | + LOGGER.info("UID bucket format: target_date={} bucket_format={} bucket_count={}", targetDate, "total-previous-salt-buckets", totalPreviousSalts); |
| 272 | + } |
| 273 | + |
210 | 274 | @Getter |
211 | 275 | public static final class Result { |
212 | 276 | private final SaltSnapshot snapshot; // can be null if new snapshot is not needed |
|
0 commit comments