Skip to content

Commit 11e35c1

Browse files
committed
Add some tests
1 parent 09c0119 commit 11e35c1

File tree

11 files changed

+1324
-117
lines changed

11 files changed

+1324
-117
lines changed

engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,6 @@
313313
<bean id="kmsKeyDaoImpl" class="org.apache.cloudstack.kms.dao.KMSKeyDaoImpl" />
314314
<bean id="kmsKekVersionDaoImpl" class="org.apache.cloudstack.kms.dao.KMSKekVersionDaoImpl" />
315315
<bean id="kmsWrappedKeyDaoImpl" class="org.apache.cloudstack.kms.dao.KMSWrappedKeyDaoImpl" />
316+
<bean id="hsmProfileDaoImpl" class="org.apache.cloudstack.kms.dao.HSMProfileDaoImpl" />
317+
<bean id="hsmProfileDetailsDaoImpl" class="org.apache.cloudstack.kms.dao.HSMProfileDetailsDaoImpl" />
316318
</beans>

framework/kms/src/main/java/org/apache/cloudstack/framework/kms/KMSException.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class KMSException extends CloudRuntimeException {
2929
* Error types for KMS operations to enable intelligent retry logic
3030
*/
3131
public enum ErrorType {
32+
CONNECTION_FAILED(true),
3233
/**
3334
* Provider not initialized or unavailable
3435
*/

framework/kms/src/main/java/org/apache/cloudstack/framework/kms/KMSProvider.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121

2222
import com.cloud.utils.component.Adapter;
2323

24-
import java.util.List;
25-
2624
/**
2725
* Abstract provider contract for Key Management Service operations.
2826
* <p>
@@ -83,14 +81,6 @@ default String createKek(KeyPurpose purpose, String label, int keyBits) throws K
8381
*/
8482
void deleteKek(String kekId) throws KMSException;
8583

86-
/**
87-
* List all KEK identifiers for a given purpose
88-
*
89-
* @param purpose the key purpose to filter by (null = all purposes)
90-
* @return list of KEK identifiers
91-
* @throws KMSException if listing fails
92-
*/
93-
List<String> listKeks(KeyPurpose purpose) throws KMSException;
9484

9585
/**
9686
* Check if a KEK exists and is accessible

plugins/kms/database/src/main/java/org/apache/cloudstack/kms/provider/DatabaseKMSProvider.java

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -183,31 +183,6 @@ public void deleteKek(String kekId) throws KMSException {
183183
}
184184
}
185185

186-
@Override
187-
public List<String> listKeks(KeyPurpose purpose) throws KMSException {
188-
try {
189-
List<String> keks = new ArrayList<>();
190-
191-
List<KMSDatabaseKekObjectVO> kekObjects;
192-
if (purpose != null) {
193-
kekObjects = kekObjectDao.listByPurpose(purpose);
194-
} else {
195-
kekObjects = kekObjectDao.listAll();
196-
}
197-
198-
for (KMSDatabaseKekObjectVO kekObject : kekObjects) {
199-
if (kekObject.getRemoved() == null) {
200-
keks.add(kekObject.getLabel());
201-
}
202-
}
203-
204-
logger.debug("listKeks called for purpose: {}. Found {} KEKs.", purpose, keks.size());
205-
return keks;
206-
} catch (Exception e) {
207-
throw KMSException.kekOperationFailed("Failed to list KEKs: " + e.getMessage(), e);
208-
}
209-
}
210-
211186
@Override
212187
public boolean isKekAvailable(String kekId) throws KMSException {
213188
try {

plugins/kms/pkcs11/src/main/java/org/apache/cloudstack/kms/provider/pkcs11/PKCS11HSMProvider.java

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,19 @@
5454
public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
5555
private static final Logger logger = LogManager.getLogger(PKCS11HSMProvider.class);
5656
private static final String PROVIDER_NAME = "pkcs11";
57-
57+
5858
@Inject
5959
private HSMProfileDao hsmProfileDao;
60-
60+
6161
@Inject
6262
private HSMProfileDetailsDao hsmProfileDetailsDao;
63-
63+
6464
@Inject
6565
private KMSKekVersionDao kmsKekVersionDao;
6666

6767
// Session pool per HSM profile
6868
private final Map<Long, HSMSessionPool> sessionPools = new ConcurrentHashMap<>();
69-
69+
7070
// Profile configuration caching
7171
private final Map<Long, Map<String, String>> profileConfigCache = new ConcurrentHashMap<>();
7272

@@ -80,6 +80,16 @@ public String getProviderName() {
8080
return PROVIDER_NAME;
8181
}
8282

83+
/**
84+
* @return The name of the component that provided this configuration
85+
* variable. This value is saved in the database so someone can easily
86+
* identify who provides this variable.
87+
**/
88+
@Override
89+
public String getConfigComponentName() {
90+
return PKCS11HSMProvider.class.getSimpleName();
91+
}
92+
8393
@Override
8494
public ConfigKey<?>[] getConfigKeys() {
8595
return new ConfigKey<?>[0];
@@ -90,7 +100,7 @@ public String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmP
90100
if (hsmProfileId == null) {
91101
throw KMSException.invalidParameter("HSM Profile ID is required for PKCS#11 provider");
92102
}
93-
103+
94104
if (StringUtils.isEmpty(label)) {
95105
label = generateKekLabel(purpose);
96106
}
@@ -142,14 +152,14 @@ public byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSExce
142152
public WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException {
143153
// 1. Unwrap with old KEK
144154
byte[] plainKey = unwrapKey(oldWrappedKey, null); // Auto-resolve old profile
145-
155+
146156
try {
147157
// 2. Wrap with new KEK
148158
Long profileId = targetHsmProfileId;
149159
if (profileId == null) {
150160
profileId = resolveProfileId(newKekLabel);
151161
}
152-
162+
153163
return wrapKey(plainKey, oldWrappedKey.getPurpose(), newKekLabel, profileId);
154164
} finally {
155165
// Zeroize plaintext key
@@ -183,16 +193,11 @@ public void deleteKek(String kekId) throws KMSException {
183193
}
184194
}
185195

186-
@Override
187-
public List<String> listKeks(KeyPurpose purpose) throws KMSException {
188-
throw new KMSException(KMSException.ErrorType.OPERATION_FAILED, "Listing KEKs directly from HSMs not supported, use DB");
189-
}
190-
191196
@Override
192197
public boolean isKekAvailable(String kekId) throws KMSException {
193198
Long hsmProfileId = resolveProfileId(kekId);
194199
if (hsmProfileId == null) return false;
195-
200+
196201
HSMSessionPool pool = getSessionPool(hsmProfileId);
197202
PKCS11Session session = null;
198203
try {
@@ -210,20 +215,20 @@ public boolean healthCheck() throws KMSException {
210215
return true;
211216
}
212217

213-
private Long resolveProfileId(String kekLabel) throws KMSException {
218+
Long resolveProfileId(String kekLabel) throws KMSException {
214219
KMSKekVersionVO version = kmsKekVersionDao.findByKekLabel(kekLabel);
215220
if (version != null && version.getHsmProfileId() != null) {
216221
return version.getHsmProfileId();
217222
}
218223
throw new KMSException(KMSException.ErrorType.KEK_NOT_FOUND, "Could not resolve HSM profile for KEK: " + kekLabel);
219224
}
220225

221-
private HSMSessionPool getSessionPool(Long profileId) {
222-
return sessionPools.computeIfAbsent(profileId,
226+
HSMSessionPool getSessionPool(Long profileId) {
227+
return sessionPools.computeIfAbsent(profileId,
223228
id -> new HSMSessionPool(id, loadProfileConfig(id)));
224229
}
225230

226-
private Map<String, String> loadProfileConfig(Long profileId) {
231+
Map<String, String> loadProfileConfig(Long profileId) {
227232
return profileConfigCache.computeIfAbsent(profileId, id -> {
228233
List<HSMProfileDetailsVO> details = hsmProfileDetailsDao.listByProfileId(id);
229234
Map<String, String> config = new HashMap<>();
@@ -238,14 +243,14 @@ private Map<String, String> loadProfileConfig(Long profileId) {
238243
});
239244
}
240245

241-
private boolean isSensitiveKey(String key) {
242-
return key.equalsIgnoreCase("pin") ||
243-
key.equalsIgnoreCase("password") ||
246+
boolean isSensitiveKey(String key) {
247+
return key.equalsIgnoreCase("pin") ||
248+
key.equalsIgnoreCase("password") ||
244249
key.toLowerCase().contains("secret") ||
245250
key.equalsIgnoreCase("private_key");
246251
}
247252

248-
private String generateKekLabel(KeyPurpose purpose) {
253+
String generateKekLabel(KeyPurpose purpose) {
249254
return purpose.getName() + "-kek-" + UUID.randomUUID().toString().substring(0, 8);
250255
}
251256

@@ -256,14 +261,14 @@ private static class HSMSessionPool {
256261
private final Map<String, String> config;
257262
private final int maxSessions;
258263
private final int minIdleSessions;
259-
264+
260265
HSMSessionPool(Long profileId, Map<String, String> config) {
261266
this.profileId = profileId;
262267
this.config = config;
263268
this.maxSessions = Integer.parseInt(config.getOrDefault("max_sessions", "10"));
264269
this.minIdleSessions = Integer.parseInt(config.getOrDefault("min_idle_sessions", "2"));
265270
this.availableSessions = new ArrayBlockingQueue<>(maxSessions);
266-
271+
267272
// Pre-warm
268273
for (int i = 0; i < minIdleSessions; i++) {
269274
try {
@@ -273,7 +278,7 @@ private static class HSMSessionPool {
273278
}
274279
}
275280
}
276-
281+
277282
PKCS11Session acquireSession(long timeoutMs) throws KMSException {
278283
try {
279284
PKCS11Session session = availableSessions.poll();
@@ -288,15 +293,15 @@ PKCS11Session acquireSession(long timeoutMs) throws KMSException {
288293
throw new KMSException(KMSException.ErrorType.CONNECTION_FAILED, "Failed to acquire HSM session", e);
289294
}
290295
}
291-
296+
292297
void releaseSession(PKCS11Session session) {
293298
if (session != null && session.isValid()) {
294299
if (!availableSessions.offer(session)) {
295300
session.close(); // Pool full
296301
}
297302
}
298303
}
299-
304+
300305
private PKCS11Session createNewSession() throws KMSException {
301306
return new PKCS11Session(config);
302307
}
@@ -307,12 +312,12 @@ private static class PKCS11Session {
307312
private final Map<String, String> config;
308313
private KeyStore keyStore;
309314
private Provider provider;
310-
315+
311316
PKCS11Session(Map<String, String> config) throws KMSException {
312317
this.config = config;
313318
connect();
314319
}
315-
320+
316321
private void connect() throws KMSException {
317322
try {
318323
String libraryPath = config.get("library_path");
@@ -324,33 +329,33 @@ private void connect() throws KMSException {
324329
throw new KMSException(KMSException.ErrorType.CONNECTION_FAILED, "Failed to connect to HSM: " + e.getMessage(), e);
325330
}
326331
}
327-
332+
328333
boolean isValid() {
329334
return true;
330335
}
331-
336+
332337
void close() {
333338
if (provider != null) {
334339
Security.removeProvider(provider.getName());
335340
}
336341
}
337-
342+
338343
String generateKey(String label, int keyBits, KeyPurpose purpose) throws KMSException {
339344
return label;
340345
}
341-
346+
342347
byte[] wrapKey(byte[] plainDek, String kekLabel) throws KMSException {
343348
return "wrapped_blob".getBytes();
344349
}
345-
350+
346351
byte[] unwrapKey(byte[] wrappedBlob, String kekLabel) throws KMSException {
347352
return new byte[32]; // 256 bits
348353
}
349-
354+
350355
void deleteKey(String label) throws KMSException {
351356
// Stub
352357
}
353-
358+
354359
boolean checkKeyExists(String label) throws KMSException {
355360
return true;
356361
}

0 commit comments

Comments
 (0)