mirror of https://github.com/apache/cloudstack.git
directly create the key in the store
This commit is contained in:
parent
6ba0af5ceb
commit
bca1b25d26
|
|
@ -47,7 +47,7 @@ import java.util.Map;
|
|||
|
||||
@APICommand(name = "addHSMProfile", description = "Adds a new HSM profile", responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
authorized = { RoleType.Admin })
|
||||
public class AddHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import javax.inject.Inject;
|
|||
|
||||
@APICommand(name = "deleteHSMProfile", description = "Deletes an HSM profile", responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
authorized = { RoleType.Admin })
|
||||
public class DeleteHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ import javax.inject.Inject;
|
|||
@APICommand(name = "updateHSMProfile", description = "Updates an HSM profile",
|
||||
responseObject = HSMProfileResponse.class,
|
||||
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
authorized = { RoleType.Admin })
|
||||
public class UpdateHSMProfileCmd extends BaseCmd {
|
||||
|
||||
@Inject
|
||||
|
|
|
|||
|
|
@ -170,9 +170,7 @@ public class KMSWrappedKeyVO {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("KMSWrappedKey %s",
|
||||
ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "kmsKeyId", "kekVersionId", "accountId", "zoneId", "state", "created",
|
||||
"removed"));
|
||||
return String.format("KMSWrappedKey %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(
|
||||
this, "id", "uuid", "kmsKeyId", "kekVersionId", "zoneId", "created", "removed"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,31 @@ import org.apache.cloudstack.framework.config.Configurable;
|
|||
*/
|
||||
public interface KMSProvider extends Configurable, Adapter {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given HSM profile configuration key name refers
|
||||
* to a
|
||||
* sensitive value (PIN, password, secret, or private key) that must be
|
||||
* encrypted at
|
||||
* rest and masked in API responses.
|
||||
*
|
||||
* <p>
|
||||
* This is a shared naming-convention helper used by both KMS providers (when
|
||||
* loading/storing profile details) and the KMS manager (when building API
|
||||
* responses).
|
||||
*
|
||||
* @param key configuration key name (case-insensitive); null returns false
|
||||
* @return true if the key is considered sensitive
|
||||
*/
|
||||
static boolean isSensitiveKey(String key) {
|
||||
if (key == null) {
|
||||
return false;
|
||||
}
|
||||
return key.equalsIgnoreCase("pin") ||
|
||||
key.equalsIgnoreCase("password") ||
|
||||
key.toLowerCase().contains("secret") ||
|
||||
key.equalsIgnoreCase("private_key");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique name of this provider
|
||||
*
|
||||
|
|
@ -43,18 +68,6 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
*/
|
||||
String getProviderName();
|
||||
|
||||
/**
|
||||
* Create a new Key Encryption Key (KEK) in the secure backend with explicit HSM profile.
|
||||
*
|
||||
* @param purpose the purpose/scope for this KEK
|
||||
* @param label human-readable label for the KEK (must be unique within purpose)
|
||||
* @param keyBits key size in bits (typically 128, 192, or 256)
|
||||
* @param hsmProfileId optional HSM profile ID to create the KEK in (null for auto-resolution/default)
|
||||
* @return the KEK identifier (label or handle) for later reference
|
||||
* @throws KMSException if KEK creation fails
|
||||
*/
|
||||
String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Create a new Key Encryption Key (KEK) in the secure backend.
|
||||
* Delegates to {@link #createKek(KeyPurpose, String, int, Long)} with null profile ID.
|
||||
|
|
@ -69,6 +82,18 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
return createKek(purpose, label, keyBits, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Key Encryption Key (KEK) in the secure backend with explicit HSM profile.
|
||||
*
|
||||
* @param purpose the purpose/scope for this KEK
|
||||
* @param label human-readable label for the KEK (must be unique within purpose)
|
||||
* @param keyBits key size in bits (typically 128, 192, or 256)
|
||||
* @param hsmProfileId optional HSM profile ID to create the KEK in (null for auto-resolution/default)
|
||||
* @return the KEK identifier (label or handle) for later reference
|
||||
* @throws KMSException if KEK creation fails
|
||||
*/
|
||||
String createKek(KeyPurpose purpose, String label, int keyBits, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Delete a KEK from the secure backend.
|
||||
* WARNING: This will make all DEKs wrapped by this KEK unrecoverable.
|
||||
|
|
@ -78,7 +103,6 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
*/
|
||||
void deleteKek(String kekId) throws KMSException;
|
||||
|
||||
|
||||
/**
|
||||
* Check if a KEK exists and is accessible
|
||||
*
|
||||
|
|
@ -88,18 +112,6 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
*/
|
||||
boolean isKekAvailable(String kekId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Wrap (encrypt) a plaintext Data Encryption Key with a KEK using explicit HSM profile.
|
||||
*
|
||||
* @param plainDek the plaintext DEK to wrap (caller must zeroize after call)
|
||||
* @param purpose the intended purpose of this DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return WrappedKey containing the encrypted DEK and metadata
|
||||
* @throws KMSException if wrapping fails or KEK not found
|
||||
*/
|
||||
WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Wrap (encrypt) a plaintext Data Encryption Key with a KEK.
|
||||
* Delegates to {@link #wrapKey(byte[], KeyPurpose, String, Long)} with null profile ID.
|
||||
|
|
@ -115,16 +127,16 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Unwrap (decrypt) a wrapped DEK to obtain the plaintext key using explicit HSM profile.
|
||||
* <p>
|
||||
* SECURITY: Caller MUST zeroize the returned byte array after use
|
||||
* Wrap (encrypt) a plaintext Data Encryption Key with a KEK using explicit HSM profile.
|
||||
*
|
||||
* @param wrappedKey the wrapped key to decrypt
|
||||
* @param plainDek the plaintext DEK to wrap (caller must zeroize after call)
|
||||
* @param purpose the intended purpose of this DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return plaintext DEK (caller must zeroize!)
|
||||
* @throws KMSException if unwrapping fails or KEK not found
|
||||
* @return WrappedKey containing the encrypted DEK and metadata
|
||||
* @throws KMSException if wrapping fails or KEK not found
|
||||
*/
|
||||
byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException;
|
||||
WrappedKey wrapKey(byte[] plainDek, KeyPurpose purpose, String kekLabel, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Unwrap (decrypt) a wrapped DEK to obtain the plaintext key.
|
||||
|
|
@ -141,18 +153,16 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Generate a new random DEK and immediately wrap it with a KEK using explicit HSM profile.
|
||||
* (convenience method combining generation + wrapping)
|
||||
* Unwrap (decrypt) a wrapped DEK to obtain the plaintext key using explicit HSM profile.
|
||||
* <p>
|
||||
* SECURITY: Caller MUST zeroize the returned byte array after use
|
||||
*
|
||||
* @param purpose the intended purpose of the new DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param keyBits DEK size in bits (typically 128, 192, or 256)
|
||||
* @param wrappedKey the wrapped key to decrypt
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return WrappedKey containing the newly generated and wrapped DEK
|
||||
* @throws KMSException if generation or wrapping fails
|
||||
* @return plaintext DEK (caller must zeroize!)
|
||||
* @throws KMSException if unwrapping fails or KEK not found
|
||||
*/
|
||||
WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits,
|
||||
Long hsmProfileId) throws KMSException;
|
||||
byte[] unwrapKey(WrappedKey wrappedKey, Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Generate a new random DEK and immediately wrap it with a KEK.
|
||||
|
|
@ -170,16 +180,18 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Rewrap a DEK with a different KEK (used during key rotation) using explicit target HSM profile.
|
||||
* This unwraps with the old KEK and wraps with the new KEK without exposing the plaintext DEK.
|
||||
* Generate a new random DEK and immediately wrap it with a KEK using explicit HSM profile.
|
||||
* (convenience method combining generation + wrapping)
|
||||
*
|
||||
* @param oldWrappedKey the currently wrapped key
|
||||
* @param newKekLabel the label of the new KEK to wrap with
|
||||
* @param targetHsmProfileId optional target HSM profile ID to wrap with (null for auto-resolution/default)
|
||||
* @return new WrappedKey encrypted with the new KEK
|
||||
* @throws KMSException if rewrapping fails
|
||||
* @param purpose the intended purpose of the new DEK
|
||||
* @param kekLabel the label of the KEK to use for wrapping
|
||||
* @param keyBits DEK size in bits (typically 128, 192, or 256)
|
||||
* @param hsmProfileId optional HSM profile ID to use (null for auto-resolution/default)
|
||||
* @return WrappedKey containing the newly generated and wrapped DEK
|
||||
* @throws KMSException if generation or wrapping fails
|
||||
*/
|
||||
WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException;
|
||||
WrappedKey generateAndWrapDek(KeyPurpose purpose, String kekLabel, int keyBits,
|
||||
Long hsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Rewrap a DEK with a different KEK (used during key rotation).
|
||||
|
|
@ -195,6 +207,18 @@ public interface KMSProvider extends Configurable, Adapter {
|
|||
return rewrapKey(oldWrappedKey, newKekLabel, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrap a DEK with a different KEK (used during key rotation) using explicit target HSM profile.
|
||||
* This unwraps with the old KEK and wraps with the new KEK without exposing the plaintext DEK.
|
||||
*
|
||||
* @param oldWrappedKey the currently wrapped key
|
||||
* @param newKekLabel the label of the new KEK to wrap with
|
||||
* @param targetHsmProfileId optional target HSM profile ID to wrap with (null for auto-resolution/default)
|
||||
* @return new WrappedKey encrypted with the new KEK
|
||||
* @throws KMSException if rewrapping fails
|
||||
*/
|
||||
WrappedKey rewrapKey(WrappedKey oldWrappedKey, String newKekLabel, Long targetHsmProfileId) throws KMSException;
|
||||
|
||||
/**
|
||||
* Perform health check on the provider backend
|
||||
*
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ import javax.annotation.PostConstruct;
|
|||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.inject.Inject;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
|
|
@ -75,6 +75,12 @@ import java.util.concurrent.TimeUnit;
|
|||
public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
||||
private static final Logger logger = LogManager.getLogger(PKCS11HSMProvider.class);
|
||||
private static final String PROVIDER_NAME = "pkcs11";
|
||||
// Security note (#7): AES-CBC provides confidentiality but not authenticity (no
|
||||
// HMAC).
|
||||
// While AES-GCM is preferred, SunPKCS11 support for GCM is often buggy or
|
||||
// missing
|
||||
// depending on the underlying driver. We rely on the HSM/storage for tamper
|
||||
// resistance.
|
||||
// AES-CBC with PKCS5Padding: FIPS-compliant (NIST SP 800-38A) with universal PKCS#11 support
|
||||
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
|
||||
|
||||
|
|
@ -228,6 +234,15 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateProfileCache(Long profileId) {
|
||||
HSMSessionPool pool = sessionPools.remove(profileId);
|
||||
if (pool != null) {
|
||||
pool.invalidate();
|
||||
}
|
||||
logger.info("Invalidated HSM session pool for profile {}", profileId);
|
||||
}
|
||||
|
||||
Long resolveProfileId(String kekLabel) throws KMSException {
|
||||
KMSKekVersionVO version = kmsKekVersionDao.findByKekLabel(kekLabel);
|
||||
if (version != null && version.getHsmProfileId() != null) {
|
||||
|
|
@ -312,11 +327,6 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
}
|
||||
}
|
||||
|
||||
String pin = config.get("pin");
|
||||
if (StringUtils.isEmpty(pin)) {
|
||||
throw KMSException.invalidParameter("pin is required for PKCS#11 HSM profile");
|
||||
}
|
||||
|
||||
File libraryFile = new File(libraryPath);
|
||||
if (!libraryFile.exists() && !libraryFile.isAbsolute()) {
|
||||
// The HSM library might be in the system library path
|
||||
|
|
@ -353,10 +363,7 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
}
|
||||
|
||||
boolean isSensitiveKey(String key) {
|
||||
return key.equalsIgnoreCase("pin") ||
|
||||
key.equalsIgnoreCase("password") ||
|
||||
key.toLowerCase().contains("secret") ||
|
||||
key.equalsIgnoreCase("private_key");
|
||||
return KMSProvider.isSensitiveKey(key);
|
||||
}
|
||||
|
||||
String generateKekLabel(KeyPurpose purpose) {
|
||||
|
|
@ -373,15 +380,6 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
return new ConfigKey<?>[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateProfileCache(Long profileId) {
|
||||
HSMSessionPool pool = sessionPools.remove(profileId);
|
||||
if (pool != null) {
|
||||
pool.invalidate();
|
||||
}
|
||||
logger.info("Invalidated HSM session pool for profile {}", profileId);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface SessionOperation<T> {
|
||||
T execute(PKCS11Session session) throws KMSException;
|
||||
|
|
@ -404,11 +402,6 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
this.availableSessions = new ArrayBlockingQueue<>(maxSessions);
|
||||
}
|
||||
|
||||
private PKCS11Session createNewSession() throws KMSException {
|
||||
// Config (including decrypted PIN) is loaded fresh each time and not stored.
|
||||
return new PKCS11Session(provider.loadProfileConfig(profileId));
|
||||
}
|
||||
|
||||
PKCS11Session acquireSession(long timeoutMs) throws KMSException {
|
||||
// Try to get an existing idle session first (no semaphore change: it already owns a permit).
|
||||
PKCS11Session session = availableSessions.poll();
|
||||
|
|
@ -451,6 +444,11 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
}
|
||||
}
|
||||
|
||||
private PKCS11Session createNewSession() throws KMSException {
|
||||
// Config (including decrypted PIN) is loaded fresh each time and not stored.
|
||||
return new PKCS11Session(provider.loadProfileConfig(profileId));
|
||||
}
|
||||
|
||||
void releaseSession(PKCS11Session session) {
|
||||
if (session == null) return;
|
||||
if (!invalidated && session.isValid() && availableSessions.offer(session)) {
|
||||
|
|
@ -725,15 +723,8 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
/**
|
||||
* Closes the PKCS#11 session and cleans up resources.
|
||||
*
|
||||
* <p>This method:
|
||||
* <ol>
|
||||
* <li>Closes the KeyStore (if it implements Closeable)</li>
|
||||
* <li>Logs out from the HSM token</li>
|
||||
* <li>Removes the provider from Security</li>
|
||||
* <li>Clears all references</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>Note: Errors during cleanup are logged but do not throw exceptions
|
||||
* <p>
|
||||
* Note: Errors during cleanup are logged but do not throw exceptions
|
||||
* to ensure cleanup continues even if some steps fail.
|
||||
*/
|
||||
void close() {
|
||||
|
|
@ -770,25 +761,26 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
/**
|
||||
* Generates an AES key directly in the HSM with the specified label.
|
||||
*
|
||||
* <p>Implementation note: Due to limitations in the Java PKCS#11 API, this method:
|
||||
* <ol>
|
||||
* <li>Generates a secure random key in software using SecureRandom</li>
|
||||
* <li>Imports it into the HSM via KeyStore.setEntry() with the label</li>
|
||||
* <li>Clears the key material from memory immediately</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* This method generates the key natively inside the HSM using a
|
||||
* {@link KeyGenerator} configured with the PKCS#11 provider, so the key
|
||||
* material never leaves the HSM boundary. The returned PKCS#11-native key
|
||||
* reference ({@code P11Key}) is then stored in the KeyStore under the
|
||||
* requested label.
|
||||
*
|
||||
* <p>While the key is briefly in software memory, this is necessary because:
|
||||
* <ul>
|
||||
* <li>Java's PKCS#11 provider doesn't support setting CKA_LABEL during generation</li>
|
||||
* <li>Keys generated via KeyGenerator have no label and can't be retrieved later</li>
|
||||
* <li>The key material is immediately cleared after import</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Using {@code KeyGenerator} with the HSM provider is required for
|
||||
* HSMs such as NetHSM that do not support importing raw secret-key bytes
|
||||
* via {@code KeyStore.setKeyEntry()}. By generating the key on the HSM first,
|
||||
* the value passed to {@code setKeyEntry()} is already a PKCS#11 token object,
|
||||
* so no raw-bytes import is attempted.
|
||||
*
|
||||
* <p>Once imported, the key:
|
||||
* <p>
|
||||
* Once stored, the key:
|
||||
* <ul>
|
||||
* <li>Resides permanently in the HSM token storage</li>
|
||||
* <li>Is marked as non-extractable (CKA_EXTRACTABLE=false) by the HSM</li>
|
||||
* <li>Can only be used for cryptographic operations via the HSM</li>
|
||||
* <li>Resides permanently in the HSM token storage</li>
|
||||
* <li>Is marked as non-extractable (CKA_EXTRACTABLE=false) by the HSM</li>
|
||||
* <li>Can only be used for cryptographic operations via the HSM</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param label Unique label for the key in the HSM
|
||||
|
|
@ -800,36 +792,35 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
String generateKey(String label, int keyBits, KeyPurpose purpose) throws KMSException {
|
||||
validateKeySize(keyBits);
|
||||
|
||||
byte[] keyBytes = null;
|
||||
try {
|
||||
// Check if key with this label already exists
|
||||
if (keyStore.containsAlias(label)) {
|
||||
throw KMSException.keyAlreadyExists("Key with label '" + label + "' already exists in HSM");
|
||||
}
|
||||
|
||||
// Generate cryptographically secure random key material
|
||||
// Using SecureRandom instead of HSM generation due to Java PKCS#11 API limitations
|
||||
keyBytes = new byte[keyBits / 8];
|
||||
SecureRandom.getInstanceStrong().nextBytes(keyBytes);
|
||||
// Generate the AES key natively inside the HSM using the PKCS#11 provider.
|
||||
// This avoids importing raw key bytes into the HSM, which is not supported
|
||||
// by all HSMs (e.g. NetHSM rejects SecretKeySpec via storeSkey()).
|
||||
// The resulting key is a PKCS#11-native P11Key that lives inside the token.
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES", provider);
|
||||
keyGen.init(keyBits);
|
||||
SecretKey hsmKey = keyGen.generateKey();
|
||||
|
||||
// Wrap key bytes in a SecretKeySpec for import into HSM
|
||||
SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");
|
||||
// Associate the HSM-generated key with the requested label by storing
|
||||
// it in the PKCS#11 KeyStore. Because hsmKey is already a P11Key
|
||||
// (not a software SecretKeySpec), P11KeyStore.storeSkey() stores it
|
||||
// as a persistent token object (CKA_TOKEN=true) with CKA_LABEL=label
|
||||
// without attempting any raw-bytes conversion.
|
||||
keyStore.setKeyEntry(label, hsmKey, null, null);
|
||||
|
||||
// Import into PKCS#11 KeyStore with label
|
||||
// Uses setKeyEntry(String, Key, char[], Certificate[]) which is the only
|
||||
// variant supported by P11KeyStore (the byte[] variant throws UnsupportedOperationException)
|
||||
// The P11KeyStore will internally convert the SecretKeySpec to a P11 token object with:
|
||||
// - CKA_TOKEN=true, CKA_LABEL=label, CKA_EXTRACTABLE=false
|
||||
keyStore.setKeyEntry(label, secretKey, null, null);
|
||||
|
||||
logger.info("Generated and imported AES-{} key '{}' into HSM (purpose: {})",
|
||||
logger.info("Generated AES-{} key '{}' in HSM (purpose: {})",
|
||||
keyBits, label, purpose);
|
||||
return label;
|
||||
|
||||
} catch (KeyStoreException e) {
|
||||
handlePKCS11Exception(e, "Failed to import key into HSM KeyStore");
|
||||
handlePKCS11Exception(e, "Failed to store key in HSM KeyStore");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
handlePKCS11Exception(e, "SecureRandom algorithm not available or key not retrievable");
|
||||
handlePKCS11Exception(e, "AES KeyGenerator not available via PKCS#11 provider");
|
||||
} catch (Exception e) {
|
||||
String errorMsg = e.getMessage();
|
||||
if (errorMsg != null && (errorMsg.contains("CKR_OBJECT_HANDLE_INVALID")
|
||||
|
|
@ -838,13 +829,8 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
} else {
|
||||
handlePKCS11Exception(e, "Failed to generate key in HSM");
|
||||
}
|
||||
} finally {
|
||||
// Immediately clear sensitive key material from memory
|
||||
if (keyBytes != null) {
|
||||
Arrays.fill(keyBytes, (byte) 0);
|
||||
}
|
||||
}
|
||||
return null; // Unreachable
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -886,22 +872,15 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
throw KMSException.invalidParameter("Plain DEK cannot be null or empty");
|
||||
}
|
||||
|
||||
SecretKey kek = null;
|
||||
SecretKey kek = getKekFromKeyStore(kekLabel);
|
||||
try {
|
||||
kek = getKekFromKeyStore(kekLabel);
|
||||
|
||||
// Generate random IV for CBC
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
|
||||
// Create cipher with AES-CBC
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, provider);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, kek, new IvParameterSpec(iv));
|
||||
|
||||
// Encrypt the plaintext DEK
|
||||
byte[] ciphertext = cipher.doFinal(plainDek);
|
||||
|
||||
// Prepend IV to ciphertext: [IV][ciphertext]
|
||||
byte[] result = new byte[IV_LENGTH + ciphertext.length];
|
||||
System.arraycopy(iv, 0, result, 0, IV_LENGTH);
|
||||
System.arraycopy(ciphertext, 0, result, IV_LENGTH, ciphertext.length);
|
||||
|
|
@ -917,10 +896,9 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
} catch (Exception e) {
|
||||
handlePKCS11Exception(e, "Failed to wrap key with HSM");
|
||||
} finally {
|
||||
// Zeroize KEK reference (actual key material is in HSM, but clear reference)
|
||||
kek = null;
|
||||
}
|
||||
return null; // Unreachable
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -947,33 +925,29 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
} catch (KeyStoreException e) {
|
||||
handlePKCS11Exception(e, "Failed to retrieve KEK from HSM");
|
||||
}
|
||||
return null; // Unreachable
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unwraps (decrypts) a wrapped DEK using a KEK stored in the HSM.
|
||||
*
|
||||
* <p>Uses AES-CBC with PKCS5Padding (FIPS 197 + NIST SP 800-38A):
|
||||
* <ol>
|
||||
* <li>Extracts IV from the wrapped blob</li>
|
||||
* <li>Retrieves KEK from HSM using the label</li>
|
||||
* <li>Decrypts using AES-CBC</li>
|
||||
* <li>Returns plaintext DEK</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* Uses AES-CBC with PKCS5Padding. Expected format: [IV (16 bytes)][ciphertext].
|
||||
*
|
||||
* <p>Security: The returned plaintext DEK must be zeroized by the caller after use.
|
||||
*
|
||||
* <p>Expected format: [IV (16 bytes)][ciphertext]
|
||||
* <p>
|
||||
* Security: The returned plaintext DEK must be zeroized by the caller after
|
||||
* use.
|
||||
*
|
||||
* @param wrappedBlob Wrapped DEK blob (IV + ciphertext)
|
||||
* @param kekLabel Label of the KEK stored in the HSM
|
||||
* @return Plaintext DEK
|
||||
* @throws KMSException with appropriate ErrorType:
|
||||
* <ul>
|
||||
* <li>{@code INVALID_PARAMETER} if wrappedBlob is null, empty, or too short</li>
|
||||
* <li>{@code KEK_NOT_FOUND} if KEK with label doesn't exist or is not accessible</li>
|
||||
* <li>{@code WRAP_UNWRAP_FAILED} if unwrapping fails</li>
|
||||
* <li>{@code INVALID_PARAMETER} if wrappedBlob is null,
|
||||
* empty, or too short</li>
|
||||
* <li>{@code KEK_NOT_FOUND} if KEK with label doesn't
|
||||
* exist or is not accessible</li>
|
||||
* <li>{@code WRAP_UNWRAP_FAILED} if unwrapping fails</li>
|
||||
* </ul>
|
||||
*/
|
||||
byte[] unwrapKey(byte[] wrappedBlob, String kekLabel) throws KMSException {
|
||||
|
|
@ -981,27 +955,21 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
throw KMSException.invalidParameter("Wrapped blob cannot be null or empty");
|
||||
}
|
||||
|
||||
// Minimum size: IV (16) + at least one block of ciphertext (16)
|
||||
// Minimum size: IV (16 bytes) + at least one AES block (16 bytes)
|
||||
if (wrappedBlob.length < IV_LENGTH + 16) {
|
||||
throw KMSException.invalidParameter("Wrapped blob too short: expected at least " +
|
||||
(IV_LENGTH + 16) + " bytes");
|
||||
}
|
||||
|
||||
SecretKey kek = null;
|
||||
SecretKey kek = getKekFromKeyStore(kekLabel);
|
||||
try {
|
||||
kek = getKekFromKeyStore(kekLabel);
|
||||
|
||||
// Extract IV and ciphertext from wrapped blob
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
System.arraycopy(wrappedBlob, 0, iv, 0, IV_LENGTH);
|
||||
byte[] ciphertext = new byte[wrappedBlob.length - IV_LENGTH];
|
||||
System.arraycopy(wrappedBlob, IV_LENGTH, ciphertext, 0, ciphertext.length);
|
||||
|
||||
// Create cipher with AES-CBC
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, provider);
|
||||
cipher.init(Cipher.DECRYPT_MODE, kek, new IvParameterSpec(iv));
|
||||
|
||||
// Decrypt the ciphertext to get plaintext DEK
|
||||
byte[] plainDek = cipher.doFinal(ciphertext);
|
||||
|
||||
logger.debug("Unwrapped key with KEK '{}' using AES-CBC", kekLabel);
|
||||
|
|
@ -1018,13 +986,11 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
} catch (Exception e) {
|
||||
handlePKCS11Exception(e, "Failed to unwrap key with HSM");
|
||||
} finally {
|
||||
// Zeroize KEK reference
|
||||
kek = null;
|
||||
}
|
||||
return null; // Unreachable
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes a key from the HSM.
|
||||
*
|
||||
|
|
@ -1040,12 +1006,10 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
*/
|
||||
void deleteKey(String label) throws KMSException {
|
||||
try {
|
||||
// Check if key exists first
|
||||
if (!keyStore.containsAlias(label)) {
|
||||
throw KMSException.kekNotFound("Key with label '" + label + "' not found in HSM");
|
||||
}
|
||||
|
||||
// Delete key from KeyStore
|
||||
keyStore.deleteEntry(label);
|
||||
|
||||
logger.debug("Deleted key '{}' from HSM", label);
|
||||
|
|
@ -1074,7 +1038,6 @@ public class PKCS11HSMProvider extends AdapterBase implements KMSProvider {
|
|||
*/
|
||||
boolean checkKeyExists(String label) throws KMSException {
|
||||
try {
|
||||
// Try to retrieve key from HSM KeyStore
|
||||
Key key = keyStore.getKey(label, null);
|
||||
return key != null;
|
||||
} catch (KeyStoreException e) {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,12 @@ import com.cloud.utils.component.ManagerBase;
|
|||
import com.cloud.utils.component.PluggableService;
|
||||
import com.cloud.utils.crypt.DBEncryptionUtil;
|
||||
import com.cloud.utils.db.Filter;
|
||||
import com.cloud.utils.db.GlobalLock;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
import com.cloud.utils.db.Transaction;
|
||||
import com.cloud.utils.db.TransactionCallbackWithException;
|
||||
import com.cloud.utils.db.TransactionStatus;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import org.apache.cloudstack.api.ApiCommandResourceType;
|
||||
import org.apache.cloudstack.api.command.admin.kms.MigrateVolumesToKMSCmd;
|
||||
|
|
@ -69,7 +73,7 @@ import org.apache.cloudstack.kms.dao.HSMProfileDetailsDao;
|
|||
import org.apache.cloudstack.kms.dao.KMSKekVersionDao;
|
||||
import org.apache.cloudstack.kms.dao.KMSKeyDao;
|
||||
import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.cloudstack.secret.PassphraseVO;
|
||||
import org.apache.cloudstack.secret.dao.PassphraseDao;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
|
@ -83,19 +87,22 @@ import java.util.Arrays;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class KMSManagerImpl extends ManagerBase implements KMSManager, PluggableService {
|
||||
private static final Logger logger = LogManager.getLogger(KMSManagerImpl.class);
|
||||
private static final Map<String, KMSProvider> kmsProviderMap = new HashMap<>();
|
||||
private final ExecutorService kmsOperationExecutor = Executors.newCachedThreadPool(r -> {
|
||||
private final ExecutorService kmsOperationExecutor = new ThreadPoolExecutor(
|
||||
2, 100, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), r -> {
|
||||
Thread t = new Thread(r, "kms-operation");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
|
|
@ -119,7 +126,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
@Inject
|
||||
private PassphraseDao passphraseDao;
|
||||
private List<KMSProvider> kmsProviders;
|
||||
private Timer rewrapTimer;
|
||||
private ScheduledExecutorService rewrapExecutor;
|
||||
|
||||
@Override
|
||||
public List<? extends KMSProvider> listKMSProviders() {
|
||||
|
|
@ -585,12 +592,11 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
throw new InvalidParameterValueException("Cannot delete KMS key: " + key + ". " +
|
||||
"There are Volumes which still reference this key");
|
||||
}
|
||||
checkKmsKeyAccess(caller, key);
|
||||
logger.info("Deleted KMS key {}", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEY_CREATE, eventDescription = "rotating KMS key", async = true)
|
||||
@ActionEvent(eventType = EventTypes.EVENT_KMS_KEY_ROTATE, eventDescription = "rotating KMS key", async = true)
|
||||
public String rotateKMSKey(RotateKMSKeyCmd cmd) throws KMSException {
|
||||
Account caller = CallContext.current().getCallingAccount();
|
||||
Integer keyBits = cmd.getKeyBits();
|
||||
|
|
@ -632,7 +638,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
kmsWrappedKeyDao.countByKmsKeyId(kmsKey.getId()));
|
||||
|
||||
ActionEventUtils.onCompletedActionEvent(CallContext.current().getCallingUserId(), kmsKey.getAccountId(),
|
||||
EventVO.LEVEL_INFO, EventTypes.EVENT_KMS_KEY_CREATE,
|
||||
EventVO.LEVEL_INFO, EventTypes.EVENT_KMS_KEY_ROTATE,
|
||||
String.format("KMS key rotation completed for KMS key from version %d to version %d",
|
||||
currentActive.getVersionNumber(), newVersion.getVersionNumber()),
|
||||
kmsKey.getId(), ApiCommandResourceType.KmsKey.toString(), CallContext.current().getStartEventId());
|
||||
|
|
@ -661,23 +667,45 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
String finalNewKekLabel = newKekLabel;
|
||||
Long newProfileId = newHSMProfile.getId();
|
||||
final HSMProfileVO finalHSMProfile = newHSMProfile;
|
||||
String newKekId = retryOperation(
|
||||
() -> provider.createKek(kmsKey.getPurpose(), finalNewKekLabel, keyBits, newProfileId));
|
||||
|
||||
KMSKekVersionVO newVersion = createKekVersion(kmsKey.getId(), newKekId, newProfileId);
|
||||
try {
|
||||
KMSKekVersionVO newVersion = Transaction
|
||||
.execute(new TransactionCallbackWithException<KMSKekVersionVO, KMSException>() {
|
||||
@Override
|
||||
public KMSKekVersionVO doInTransaction(TransactionStatus status) throws KMSException {
|
||||
KMSKekVersionVO version = createKekVersion(kmsKey.getId(), newKekId, newProfileId);
|
||||
|
||||
if (!newProfileId.equals(kmsKey.getHsmProfileId())) {
|
||||
kmsKey.setHsmProfileId(newProfileId);
|
||||
kmsKeyDao.update(kmsKey.getId(), kmsKey);
|
||||
logger.info("Updated KMS key {} to use HSM profile {}", kmsKey, newHSMProfile);
|
||||
if (!newProfileId.equals(kmsKey.getHsmProfileId())) {
|
||||
kmsKey.setHsmProfileId(newProfileId);
|
||||
kmsKeyDao.update(kmsKey.getId(), kmsKey);
|
||||
logger.info("Updated KMS key {} to use HSM profile {}", kmsKey, finalHSMProfile);
|
||||
}
|
||||
return version;
|
||||
}
|
||||
});
|
||||
|
||||
logger.info("KEK rotation: KMS key {} now has {} versions (active: v{}, previous: v{})",
|
||||
kmsKey, newVersion.getVersionNumber(), newVersion.getVersionNumber(),
|
||||
newVersion.getVersionNumber() - 1);
|
||||
|
||||
return newKekId;
|
||||
} catch (KMSException e) {
|
||||
logger.error(
|
||||
"Database update failed during KEK rotation for kmsKey {}. Attempting to delete orphaned KEK "
|
||||
+ "{} from provider {}",
|
||||
kmsKey, newKekId, provider.getProviderName());
|
||||
try {
|
||||
provider.deleteKek(newKekId);
|
||||
} catch (KMSException ex) {
|
||||
logger.error("Failed to delete orphaned KEK {} from provider {} after DB failure: {}",
|
||||
newKekId, provider.getProviderName(), ex.getMessage());
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
logger.info("KEK rotation: KMS key {} now has {} versions (active: v{}, previous: v{})",
|
||||
kmsKey, newVersion.getVersionNumber(), newVersion.getVersionNumber(),
|
||||
newVersion.getVersionNumber() - 1);
|
||||
|
||||
return newKekId;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("KEK rotation failed for kmsKey {}: {}", kmsKey, e.getMessage());
|
||||
throw handleKmsException(e);
|
||||
|
|
@ -830,7 +858,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
if (passphrase == null) {
|
||||
logger.warn(
|
||||
"Skipping migration of volume from to the KMS key {} because passphrase id: {} not found for "
|
||||
+ "volume {}}",
|
||||
+ "volume {}",
|
||||
kmsKey, volume.getPassphraseId(), volume);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1214,10 +1242,7 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
|
||||
boolean isSensitiveKey(String key) {
|
||||
return key.equalsIgnoreCase("pin") ||
|
||||
key.equalsIgnoreCase("password") ||
|
||||
key.toLowerCase().contains("secret") ||
|
||||
key.equalsIgnoreCase("private_key");
|
||||
return KMSProvider.isSensitiveKey(key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1394,7 +1419,18 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
}
|
||||
|
||||
private void scheduleRewrapWorker() {
|
||||
final ManagedContextTimerTask rewrapTask = new ManagedContextTimerTask() {
|
||||
long intervalMs = KMSRewrapIntervalMs.value();
|
||||
if (intervalMs <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
rewrapExecutor = Executors.newScheduledThreadPool(1, r -> {
|
||||
Thread t = new Thread(r, "KMSRewrapWorker");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
rewrapExecutor.scheduleAtFixedRate(new ManagedContextRunnable() {
|
||||
@Override
|
||||
protected void runInContext() {
|
||||
try {
|
||||
|
|
@ -1403,11 +1439,8 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
logger.error("Error while running KMS rewrap worker", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, 10000L, intervalMs, TimeUnit.MILLISECONDS);
|
||||
|
||||
long intervalMs = KMSRewrapIntervalMs.value();
|
||||
rewrapTimer = new Timer("KMSRewrapWorker", true); // daemon so it doesn't block JVM shutdown
|
||||
rewrapTimer.schedule(rewrapTask, 10000L, intervalMs);
|
||||
logger.info("KMS rewrap worker scheduled with interval: {} ms", intervalMs);
|
||||
}
|
||||
|
||||
|
|
@ -1416,28 +1449,41 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
* using the active version.
|
||||
*/
|
||||
private void processRewrapBatch() {
|
||||
GlobalLock lock = GlobalLock.getInternLock("kms.rewrap.worker");
|
||||
try {
|
||||
List<KMSKekVersionVO> previousVersions = kmsKekVersionDao.findByStatus(KMSKekVersionVO.Status.Previous);
|
||||
|
||||
if (previousVersions.isEmpty()) {
|
||||
logger.trace("No KEK versions pending rewrap");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Found {} KEK version(s) with status Previous - processing rewrap batches",
|
||||
previousVersions.size());
|
||||
|
||||
int batchSize = KMSRewrapBatchSize.value();
|
||||
|
||||
for (KMSKekVersionVO oldVersion : previousVersions) {
|
||||
if (lock.lock(5)) {
|
||||
try {
|
||||
processVersionRewrap(oldVersion, batchSize);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error processing rewrap for KEK version {}: {}", oldVersion, e.getMessage(), e);
|
||||
List<KMSKekVersionVO> previousVersions = kmsKekVersionDao
|
||||
.findByStatus(KMSKekVersionVO.Status.Previous);
|
||||
|
||||
if (previousVersions.isEmpty()) {
|
||||
logger.trace("No KEK versions pending rewrap");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug("Found {} KEK version(s) with status Previous - processing rewrap batches",
|
||||
previousVersions.size());
|
||||
|
||||
int batchSize = KMSRewrapBatchSize.value();
|
||||
|
||||
for (KMSKekVersionVO oldVersion : previousVersions) {
|
||||
try {
|
||||
processVersionRewrap(oldVersion, batchSize);
|
||||
} catch (Exception e) {
|
||||
logger.error("Error processing rewrap for KEK version {}: {}", oldVersion, e.getMessage(),
|
||||
e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
} else {
|
||||
logger.trace("KMS rewrap worker: could not acquire cluster lock, skipping batch");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Error in rewrap worker: {}", e.getMessage(), e);
|
||||
} finally {
|
||||
lock.releaseRef();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1457,12 +1503,26 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
List<KMSWrappedKeyVO> keysToRewrap = kmsWrappedKeyDao.listByKekVersionId(oldVersion.getId(), batchSize);
|
||||
|
||||
if (keysToRewrap.isEmpty()) {
|
||||
logger.info("All wrapped keys rewrapped for KEK version {} (v{}) - archiving",
|
||||
logger.info("All wrapped keys rewrapped for KEK version {} (v{}) - archiving and deleting from provider",
|
||||
oldVersion.getUuid(), oldVersion.getVersionNumber());
|
||||
|
||||
oldVersion.setStatus(KMSKekVersionVO.Status.Archived);
|
||||
kmsKekVersionDao.update(oldVersion.getId(), oldVersion);
|
||||
|
||||
// Delete the old KEK from the HSM since no wrapped keys reference it anymore
|
||||
try {
|
||||
HSMProfileVO oldProfile = hsmProfileDao.findById(oldVersion.getHsmProfileId());
|
||||
if (oldProfile != null) {
|
||||
KMSProvider provider = getKMSProvider(oldProfile.getProtocol());
|
||||
provider.deleteKek(oldVersion.getKekLabel());
|
||||
logger.info("Deleted archived KEK {} (v{}) from provider {}",
|
||||
oldVersion.getKekLabel(), oldVersion.getVersionNumber(), provider.getProviderName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to delete archived KEK {} (v{}) from provider: {}",
|
||||
oldVersion.getKekLabel(), oldVersion.getVersionNumber(), e.getMessage());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1513,9 +1573,9 @@ public class KMSManagerImpl extends ManagerBase implements KMSManager, Pluggable
|
|||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
if (rewrapTimer != null) {
|
||||
rewrapTimer.cancel();
|
||||
rewrapTimer = null;
|
||||
if (rewrapExecutor != null) {
|
||||
rewrapExecutor.shutdownNow();
|
||||
rewrapExecutor = null;
|
||||
}
|
||||
kmsOperationExecutor.shutdownNow();
|
||||
return super.stop();
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.apache.cloudstack.kms;
|
||||
|
||||
import com.cloud.api.ApiResponseHelper;
|
||||
import com.cloud.dc.dao.DataCenterDao;
|
||||
import com.cloud.domain.dao.DomainDao;
|
||||
import com.cloud.user.AccountManager;
|
||||
|
|
@ -27,6 +28,8 @@ import org.junit.Test;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
|
|
@ -130,7 +133,6 @@ public class KMSManagerImplHSMTest {
|
|||
when(profile.getUuid()).thenReturn("profile-uuid");
|
||||
when(profile.getName()).thenReturn("test-profile");
|
||||
when(profile.getProtocol()).thenReturn("PKCS11");
|
||||
when(profile.getAccountId()).thenReturn(testAccountId);
|
||||
when(profile.getVendorName()).thenReturn("TestVendor");
|
||||
when(profile.isEnabled()).thenReturn(true);
|
||||
when(profile.getCreated()).thenReturn(new java.util.Date());
|
||||
|
|
@ -145,15 +147,11 @@ public class KMSManagerImplHSMTest {
|
|||
|
||||
when(hsmProfileDetailsDao.listByProfileId(profileId)).thenReturn(Arrays.asList(detail1, detail2));
|
||||
|
||||
com.cloud.user.Account mockAccount = mock(com.cloud.user.Account.class);
|
||||
when(mockAccount.getUuid()).thenReturn("account-uuid");
|
||||
when(mockAccount.getAccountName()).thenReturn("testaccount");
|
||||
when(accountManager.getAccount(testAccountId)).thenReturn(mockAccount);
|
||||
try (MockedStatic<ApiResponseHelper> mockedApiResponseHelper = Mockito.mockStatic(ApiResponseHelper.class)) {
|
||||
HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile);
|
||||
|
||||
HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile);
|
||||
|
||||
assertNotNull("Response should not be null", response);
|
||||
verify(accountManager).getAccount(testAccountId);
|
||||
verify(hsmProfileDetailsDao).listByProfileId(profileId);
|
||||
assertNotNull("Response should not be null", response);
|
||||
verify(hsmProfileDetailsDao).listByProfileId(profileId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,6 +206,9 @@ export default {
|
|||
listView: true,
|
||||
popup: true,
|
||||
dataView: false,
|
||||
show: (record, store) => {
|
||||
return ['Admin'].includes(store.userInfo.roletype)
|
||||
},
|
||||
args: (record, store, group) => {
|
||||
return ['Admin'].includes(store.userInfo.roletype)
|
||||
? ['name', 'zoneid', 'vendorname', 'domainid', 'account', 'projectid', 'details', 'system']
|
||||
|
|
@ -224,6 +227,9 @@ export default {
|
|||
label: 'label.update.hsm.profile',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
show: (record, store) => {
|
||||
return ['Admin'].includes(store.userInfo.roletype)
|
||||
},
|
||||
args: ['id', 'name', 'enabled'],
|
||||
mapping: {
|
||||
id: {
|
||||
|
|
@ -239,6 +245,9 @@ export default {
|
|||
message: 'message.action.delete.hsm.profile',
|
||||
dataView: true,
|
||||
popup: true,
|
||||
show: (record, store) => {
|
||||
return ['Admin'].includes(store.userInfo.roletype)
|
||||
},
|
||||
args: ['id'],
|
||||
mapping: {
|
||||
id: {
|
||||
|
|
|
|||
Loading…
Reference in New Issue