Skip to content

Commit c1fdd07

Browse files
committed
rotate keys wrapped with older versions
1 parent 7afdd31 commit c1fdd07

File tree

8 files changed

+321
-298
lines changed

8 files changed

+321
-298
lines changed

api/src/main/java/org/apache/cloudstack/kms/KMSManager.java

Lines changed: 36 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,32 @@ public interface KMSManager extends Manager, Configurable {
127127
ConfigKey.Scope.Global
128128
);
129129

130+
/**
131+
* Global: batch size for background rewrap operations
132+
*/
133+
ConfigKey<Integer> KMSRewrapBatchSize = new ConfigKey<>(
134+
"Advanced",
135+
Integer.class,
136+
"kms.rewrap.batch.size",
137+
"50",
138+
"Number of wrapped keys to rewrap per batch in background job",
139+
true,
140+
ConfigKey.Scope.Global
141+
);
142+
143+
/**
144+
* Global: interval for background rewrap job
145+
*/
146+
ConfigKey<Long> KMSRewrapIntervalMs = new ConfigKey<>(
147+
"Advanced",
148+
Long.class,
149+
"kms.rewrap.interval.ms",
150+
"300000",
151+
"Interval in milliseconds between background rewrap job executions (default: 5 minutes)",
152+
true,
153+
ConfigKey.Scope.Global
154+
);
155+
130156
// ==================== Provider Management ====================
131157

132158
/**
@@ -161,63 +187,6 @@ public interface KMSManager extends Manager, Configurable {
161187
*/
162188
boolean isKmsEnabled(Long zoneId);
163189

164-
// ==================== KEK Management ====================
165-
166-
/**
167-
* Create a new KEK for a zone and purpose
168-
*
169-
* @param zoneId the zone ID
170-
* @param purpose the key purpose
171-
* @param label optional custom label (null for auto-generated)
172-
* @param keyBits key size in bits
173-
* @return the KEK identifier
174-
* @throws KMSException if creation fails
175-
*/
176-
String createKek(Long zoneId, KeyPurpose purpose, String label, int keyBits) throws KMSException;
177-
178-
/**
179-
* Delete a KEK (WARNING: makes all DEKs wrapped by it unrecoverable)
180-
*
181-
* @param zoneId the zone ID
182-
* @param kekId the KEK identifier
183-
* @throws KMSException if deletion fails
184-
*/
185-
void deleteKek(Long zoneId, String kekId) throws KMSException;
186-
187-
/**
188-
* List KEKs for a zone and purpose
189-
*
190-
* @param zoneId the zone ID
191-
* @param purpose the purpose filter (null for all)
192-
* @return list of KEK identifiers
193-
* @throws KMSException if listing fails
194-
*/
195-
List<String> listKeks(Long zoneId, KeyPurpose purpose) throws KMSException;
196-
197-
/**
198-
* Check if a KEK is available
199-
*
200-
* @param zoneId the zone ID
201-
* @param kekId the KEK identifier
202-
* @return true if available
203-
* @throws KMSException if check fails
204-
*/
205-
boolean isKekAvailable(Long zoneId, String kekId) throws KMSException;
206-
207-
/**
208-
* Rotate a KEK (create new one and rewrap all DEKs)
209-
*
210-
* @param zoneId the zone ID
211-
* @param purpose the purpose
212-
* @param oldKekLabel the old KEK label (must be specified)
213-
* @param newKekLabel the new KEK label (null for auto-generated)
214-
* @param keyBits the new KEK size
215-
* @return the new KEK identifier
216-
* @throws KMSException if rotation fails
217-
*/
218-
String rotateKek(Long zoneId, KeyPurpose purpose, String oldKekLabel,
219-
String newKekLabel, int keyBits) throws KMSException;
220-
221190
// ==================== DEK Operations ====================
222191

223192
/**
@@ -233,15 +202,6 @@ String rotateKek(Long zoneId, KeyPurpose purpose, String oldKekLabel,
233202

234203
// ==================== Health & Status ====================
235204

236-
/**
237-
* Check KMS provider health for a zone
238-
*
239-
* @param zoneId the zone ID (null for global)
240-
* @return true if healthy
241-
* @throws KMSException if health check fails critically
242-
*/
243-
boolean healthCheck(Long zoneId) throws KMSException;
244-
245205
// ==================== User KEK Management ====================
246206

247207
/**
@@ -274,20 +234,11 @@ KMSKey createUserKMSKey(Long accountId, Long domainId, Long zoneId,
274234
List<? extends KMSKey> listUserKMSKeys(Long accountId, Long domainId, Long zoneId,
275235
KeyPurpose purpose, KMSKey.State state);
276236

277-
/**
278-
* Get a KMS key by UUID (with permission check)
279-
*
280-
* @param uuid the key UUID
281-
* @param callerAccountId the caller's account ID
282-
* @return the KMS key, or null if not found or no permission
283-
*/
284-
KMSKey getUserKMSKey(String uuid, Long callerAccountId);
285-
286237
/**
287238
* Check if caller has permission to use a KMS key
288239
*
289240
* @param callerAccountId the caller's account ID
290-
* @param keyUuid the key UUID
241+
* @param key the KMS key
291242
* @return true if caller has permission
292243
*/
293244
boolean hasPermission(Long callerAccountId, KMSKey key);
@@ -305,7 +256,7 @@ List<? extends KMSKey> listUserKMSKeys(Long accountId, Long domainId, Long zoneI
305256
/**
306257
* Generate and wrap a DEK using a specific KMS key UUID
307258
*
308-
* @param kekUuid the KMS key UUID
259+
* @param kmsKey the KMS key
309260
* @param callerAccountId the caller's account ID
310261
* @return wrapped key ready for database storage
311262
* @throws KMSException if operation fails
@@ -364,17 +315,6 @@ List<? extends KMSKey> listUserKMSKeys(Long accountId, Long domainId, Long zoneI
364315
*/
365316
String rotateKMSKey(RotateKMSKeyCmd cmd) throws KMSException;
366317

367-
/**
368-
* Gradually rewrap all wrapped keys for a KMS key to use new KEK version
369-
*
370-
* @param kmsKeyId KMS key ID
371-
* @param newKekVersionId New active KEK version ID
372-
* @param batchSize Number of keys to process per batch
373-
* @return Number of keys successfully rewrapped
374-
* @throws KMSException if rewrap fails
375-
*/
376-
int rewrapWrappedKeysForKMSKey(Long kmsKeyId, Long newKekVersionId, int batchSize) throws KMSException;
377-
378318
/**
379319
* Migrate passphrase-based volumes to KMS encryption
380320
*
@@ -383,4 +323,12 @@ List<? extends KMSKey> listUserKMSKeys(Long accountId, Long domainId, Long zoneI
383323
* @throws KMSException if migration fails
384324
*/
385325
int migrateVolumesToKMS(MigrateVolumesToKMSCmd cmd) throws KMSException;
326+
327+
/**
328+
* Delete all KMS keys owned by an account (called during account cleanup)
329+
*
330+
* @param accountId the account ID
331+
* @return true if all keys were successfully deleted
332+
*/
333+
boolean deleteKMSKeysByAccountId(Long accountId);
386334
}

engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDao.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,10 @@ public interface KMSKekVersionDao extends GenericDao<KMSKekVersionVO, Long> {
4747
* Find a KEK version by KEK label
4848
*/
4949
KMSKekVersionVO findByKekLabel(String kekLabel);
50+
51+
/**
52+
* Find all KEK versions with a specific status
53+
* (useful for background jobs to find versions needing processing)
54+
*/
55+
List<KMSKekVersionVO> findByStatus(KMSKekVersionVO.Status status);
5056
}

engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDaoImpl.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,11 @@ public KMSKekVersionVO findByKekLabel(String kekLabel) {
7676
sc.setParameters("kekLabel", kekLabel);
7777
return findOneBy(sc);
7878
}
79+
80+
@Override
81+
public List<KMSKekVersionVO> findByStatus(KMSKekVersionVO.Status status) {
82+
SearchCriteria<KMSKekVersionVO> sc = allFieldSearch.create();
83+
sc.setParameters("status", status);
84+
return listBy(sc);
85+
}
7986
}

engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDao.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ public interface KMSWrappedKeyDao extends GenericDao<KMSWrappedKeyVO, Long> {
6262
*/
6363
List<KMSWrappedKeyVO> listByKekVersionId(Long kekVersionId);
6464

65+
/**
66+
* List wrapped keys using a specific KEK version with pagination limit
67+
* (useful for batch processing in background jobs)
68+
*
69+
* @param kekVersionId the KEK version ID (FK to kms_kek_versions)
70+
* @param limit maximum number of keys to return
71+
* @return list of wrapped keys (limited to specified count)
72+
*/
73+
List<KMSWrappedKeyVO> listByKekVersionId(Long kekVersionId, int limit);
74+
6575
/**
6676
* List wrapped keys for a KMS key that need re-encryption (not using specified version)
6777
*

engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDaoImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.cloudstack.kms.dao;
1919

20+
import com.cloud.utils.db.Filter;
2021
import com.cloud.utils.db.GenericDaoBase;
2122
import com.cloud.utils.db.SearchBuilder;
2223
import com.cloud.utils.db.SearchCriteria;
@@ -81,6 +82,14 @@ public List<KMSWrappedKeyVO> listByKekVersionId(Long kekVersionId) {
8182
return listBy(sc);
8283
}
8384

85+
@Override
86+
public List<KMSWrappedKeyVO> listByKekVersionId(Long kekVersionId, int limit) {
87+
SearchCriteria<KMSWrappedKeyVO> sc = allFieldSearch.create();
88+
sc.setParameters("kekVersionId", kekVersionId);
89+
Filter filter = new Filter(limit);
90+
return listBy(sc, filter);
91+
}
92+
8493
@Override
8594
public List<KMSWrappedKeyVO> listWrappedKeysForRewrap(long kmsKeyId, long excludeKekVersionId) {
8695
SearchCriteria<KMSWrappedKeyVO> sc = rewrapExcludeVersionSearch.create();

engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ CREATE TABLE IF NOT EXISTS `cloud`.`kms_wrapped_key` (
114114
INDEX `idx_kms_key_id` (`kms_key_id`, `removed`),
115115
INDEX `idx_kek_version_id` (`kek_version_id`, `removed`),
116116
INDEX `idx_zone_id` (`zone_id`, `removed`),
117-
CONSTRAINT `fk_kms_wrapped_key__kms_key_id` FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`) ON DELETE RESTRICT,
118-
CONSTRAINT `fk_kms_wrapped_key__kek_version_id` FOREIGN KEY (`kek_version_id`) REFERENCES `kms_kek_versions`(`id`) ON DELETE RESTRICT,
117+
CONSTRAINT `fk_kms_wrapped_key__kms_key_id` FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`) ON DELETE CASCADE,
118+
CONSTRAINT `fk_kms_wrapped_key__kek_version_id` FOREIGN KEY (`kek_version_id`) REFERENCES `kms_kek_versions`(`id`) ON DELETE CASCADE,
119119
CONSTRAINT `fk_kms_wrapped_key__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE
120120
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KMS wrapped encryption keys (DEKs) - references kms_keys for KEK metadata and kek_versions for specific version';
121121

server/src/main/java/com/cloud/user/AccountManagerImpl.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
315315
private NetworkPermissionDao networkPermissionDao;
316316
@Inject
317317
private SslCertDao sslCertDao;
318+
@Inject
319+
private org.apache.cloudstack.kms.KMSManager kmsManager;
318320

319321
private List<QuerySelector> _querySelectors;
320322

@@ -1215,6 +1217,17 @@ public int compare(NetworkVO network1, NetworkVO network2) {
12151217
// Delete Webhooks
12161218
deleteWebhooksForAccount(accountId);
12171219

1220+
// Delete KMS keys
1221+
try {
1222+
if (!kmsManager.deleteKMSKeysByAccountId(accountId)) {
1223+
logger.warn("Failed to delete all KMS keys for account {}", account);
1224+
accountCleanupNeeded = true;
1225+
}
1226+
} catch (Exception e) {
1227+
logger.error("Error deleting KMS keys for account {}: {}", account, e.getMessage(), e);
1228+
accountCleanupNeeded = true;
1229+
}
1230+
12181231
return true;
12191232
} catch (Exception ex) {
12201233
logger.warn("Failed to cleanup account " + account + " due to ", ex);

0 commit comments

Comments
 (0)