DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.

DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
This document outlines the design for a comprehensive Key Management Service (KMS) in Apache CloudStack that provides envelope encryption for volume encryption using Hardware Security Modules (HSMs) or a database-backed fallback provider (also used for testing).
The primary goal is to allow users to manage encryption keys (KEKs) for their encrypted volumes through a secure, plugin-based architecture. KEKs can be stored in PKCS#11-compliant HSMs for hardware-grade security or in the CloudStack database for deployments without HSM hardware. The design supports key rotation, cross-HSM migration, and gradual re-encryption of wrapped keys.
GitHub PR: https://github.com/apache/cloudstack/pull/12711
CloudStack Version: 4.23.0.0
createKMSKey.addHSMProfile.wrapped_blob in the kms_wrapped_key table.This hierarchy:
KMS Provider (Plugin) → HSM Profile (Configuration) → KMS Key (KEK abstraction) → KEK Version → Wrapped Key (DEK)
database, pkcs11. This corresponds to the plugin implementation (DatabaseKMSProvider, PKCS11HSMProvider).kms_hsm_profiles table.kms_keys table.kms_kek_versions table.kms_wrapped_key table.| What | Source | Used For |
|---|---|---|
| KEK size | keybits parameter in createKMSKey / rotateKMSKey (stored in kms_keys.key_bits) | Size of the Key Encryption Key stored in the HSM or database. Used when creating or rotating a KEK. |
| DEK size | Global config kms.dek.size.bits | Size of the Data Encryption Key generated per volume. Used in generateVolumeKeyWithKek(). |
keybits controls only the KEK size (e.g., 256-bit AES key in the HSM).kms.dek.size.bits controls the DEK size for all new encrypted volumes.HSM Profile Management (Admin only)
DBEncryptionUtil before storageKMS Key Management
Key Rotation
Transaction.execute() block; orphaned HSM keys are cleaned up on DB failureVolume Encryption Integration
kms.dek.size.bits) and wrap them with the active KEK versionPlugin Architecture
DatabaseKMSProvider: Database-backed KEK storage with AES/GCM/NoPadding encryption via DBEncryptionUtilPKCS11HSMProvider: PKCS#11 HSM integration with per-profile session pooling and AES/CBC/PKCS5Padding wrappingConcurrency & Cluster Safety
ThreadPoolExecutor(core=2, max=100, keepAlive=60s, SynchronousQueue) with daemon threadsGlobalLock("kms.rewrap.worker") prevents duplicate rewrap work across management server nodesScheduledExecutorService (replaces java.util.Timer) for robust periodic rewrap scheduling┌─────────────────────────────────────────────────────────────┐
│ CloudStack API Layer │
│ createKMSKey, listKMSKeys, rotateKMSKey, addHSMProfile │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ KMSManagerImpl (Implementation) │
│ - HSM profile management - Permission checks │
│ - KEK version management - Retry with back-off │
│ - Background rewrap jobs - Per-operation timeout │
│ - Cross-HSM migration - Transaction boundaries │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ KMSProvider (Plugin Interface) │
│ createKek(), wrapKey(), unwrapKey(), rewrapKey() │
└─────────────────────────────────────────────────────────────┘
┌────────────────────┴────────────────────┐
↓ ↓
┌──────────────────────┐ ┌──────────────────────┐
│ DatabaseKMSProvider │ │ PKCS11HSMProvider │
│ - Database storage │ │ - PKCS#11 interface │
│ - AES-256-GCM │ │ - Session pooling │
│ - Encrypted KEKs │ │ - AES/CBC/PKCS5 │
└──────────────────────┘ └──────────────────────┘
HSM (PKCS#11) or Database
└── KEK (Key Encryption Key) - Stored in HSM/DB
└── KEK Version (supports rotation)
└── DEK (Data Encryption Key) - Wrapped by KEK
└── Volume Data - Encrypted by DEK on hypervisor
account_id set → visible only to that accountzone_id set, account_id NULL → visible to all accounts in that zonezone_id NULL, account_id NULL, is_public = TRUE → visible to all accounts in all zonesNote:
createKMSKeyrequires an explicithsmprofileidparameter. There is no automatic profile resolution or fallback hierarchy during key creation.
VolumeOrchestrator calls kmsManager.generateVolumeKeyWithKek()kms.dek.size.bits), wraps it using the active KEK, and persists the wrapped keykms_key_id and kms_wrapped_key_idkmsManager.unwrapVolumeKey() is calledrotateKMSKey with optional hsmprofileid for cross-HSM migrationTransaction.execute()ScheduledExecutorService) gradually rewraps wrapped keys in batchesCreates a new KMS key (Key Encryption Key) for envelope encryption.
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
name | Yes | String | Name of the KMS key |
description | No | String | Description of the KMS key |
purpose | Yes | String | Purpose of the key (volume, tls) |
zoneid | Yes | UUID | Zone ID where the key will be valid |
hsmprofileid | Yes | UUID | HSM profile ID to create the KEK in |
keybits | No | Integer | KEK size in bits (128, 192, 256). Default: 256 |
account | No | String | Account name (admin use) |
domainid | No | UUID | Domain ID (admin use) |
Lists KMS keys available to the caller.
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
id | No | UUID | List KMS key by UUID |
purpose | No | String | Filter by purpose |
zoneid | No | UUID | Filter by zone |
state | No | String | Filter by state (Enabled, Disabled) |
Updates KMS key name, description, or state.
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
id | Yes | UUID | KMS key UUID |
name | No | String | New name |
description | No | String | New description |
enabled | No | Boolean | Enable/disable the key |
Deletes a KMS key (only if not referenced by volumes or wrapped keys).
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
id | Yes | UUID | KMS key UUID |
Rotates KEK by creating a new version and scheduling gradual re-encryption of wrapped keys.
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
id | Yes | UUID | KMS key UUID to rotate |
keybits | No | Integer | Key size for new KEK (default: same as current) |
hsmprofileid | No | UUID | Target HSM profile for cross-HSM migration |
Migrates passphrase-based volumes to KMS encryption.
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
zoneid | Yes | UUID | Zone ID |
id | Yes | UUID | KMS key ID to migrate volumes to |
account | No | String | Migrate volumes for specific account |
domainid | No | UUID | Domain ID |
Adds a new HSM profile for connecting to an HSM device.
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
name | Yes | String | HSM profile name |
protocol | No | String | Protocol (PKCS11, KMIP, etc.). Default: pkcs11 |
zoneid | No | UUID | Zone ID (null = global scope) |
domainid | No | UUID | Domain ID |
account | No | String | Account name |
| is_public | No | Boolean | Public profile (globally available, root admin only) |
vendorname | No | String | HSM vendor name |
details | No | Map | HSM configuration details |
PKCS#11 details keys: library (path to PKCS#11 library), slot (slot number), pin (HSM PIN, encrypted at rest), token_label (token label), minSessions, maxSessions
Lists HSM profiles visible to the caller.
ENC(...))Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
id | No | UUID | HSM profile ID |
zoneid | No | UUID | Zone ID |
protocol | No | String | Protocol filter |
enabled | No | Boolean | Enabled filter |
Updates an HSM profile name or enabled state.
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
id | Yes | UUID | HSM profile UUID |
name | No | String | New name |
enabled | No | Boolean | Enable/disable |
Deletes an HSM profile (only if not in use by any KEK versions).
Parameters:
| Parameter | Required | Type | Description |
|---|---|---|---|
id | Yes | UUID | HSM profile UUID |
| Setting Key | Scope | Type | Default | Description |
|---|---|---|---|---|
kms.dek.size.bits | Global | Integer | 256 | Size of DEKs in bits for new volumes (128, 192, 256) |
kms.retry.count | Global | Integer | 3 | Number of retry attempts for transient KMS failures |
kms.retry.delay.ms | Global | Integer | 1000 | Delay in milliseconds between retry attempts |
kms.operation.timeout.sec | Global | Integer | 30 | Per-attempt timeout for KMS operations |
kms.rewrap.batch.size | Global | Integer | 50 | Wrapped keys rewrapped per batch in background job |
kms.rewrap.interval.ms | Global | Long | 300000 | Interval between background rewrap executions (5 min) |
cloud.kms_hsm_profilesStores HSM profile configurations. Scoped by account_id, domain_id, and zone_id.
CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profiles` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(40) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`protocol` VARCHAR(32) NOT NULL COMMENT 'PKCS11, KMIP, AWS_KMS, etc.',
`account_id` BIGINT UNSIGNED COMMENT 'null = admin-provided',
`domain_id` BIGINT UNSIGNED,
`zone_id` BIGINT UNSIGNED COMMENT 'null = global scope',
`vendor_name` VARCHAR(64),
`enabled` BOOLEAN NOT NULL DEFAULT TRUE,
`is_public` BOOLEAN NOT NULL DEFAULT FALSE,
`created` DATETIME NOT NULL,
`removed` DATETIME,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_uuid` (`uuid`),
UNIQUE KEY `uk_account_name` (`account_id`, `name`, `removed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
cloud.kms_hsm_profile_detailsKey-value configuration details for HSM profiles. Sensitive values (PIN, password) are encrypted via DBEncryptionUtil.
CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profile_details` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`profile_id` BIGINT UNSIGNED NOT NULL,
`name` VARCHAR(255) NOT NULL,
`value` TEXT NOT NULL COMMENT 'encrypted if sensitive',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_profile_name` (`profile_id`, `name`),
CONSTRAINT `fk_kms_hsm_profile_details__profile_id`
FOREIGN KEY (`profile_id`) REFERENCES `kms_hsm_profiles`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
cloud.kms_keysKMS key (KEK) metadata. Account-scoped, zone-bound.
CREATE TABLE IF NOT EXISTS `cloud`.`kms_keys` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(40) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` VARCHAR(1024),
`kek_label` VARCHAR(255) NOT NULL,
`purpose` VARCHAR(32) NOT NULL COMMENT 'VOLUME_ENCRYPTION, TLS_CERT, CONFIG_SECRET',
`account_id` BIGINT UNSIGNED NOT NULL,
`domain_id` BIGINT UNSIGNED NOT NULL,
`zone_id` BIGINT UNSIGNED NOT NULL,
`algorithm` VARCHAR(64) NOT NULL DEFAULT 'AES/GCM/NoPadding',
`key_bits` INT NOT NULL DEFAULT 256,
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
`hsm_profile_id` BIGINT UNSIGNED NOT NULL,
`created` DATETIME NOT NULL,
`removed` DATETIME,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_uuid` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
cloud.kms_kek_versionsKEK versions for gradual key rotation. Only one Active version per KMS key at a time.
CREATE TABLE IF NOT EXISTS `cloud`.`kms_kek_versions` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(40) NOT NULL,
`kms_key_id` BIGINT UNSIGNED NOT NULL,
`version_number` INT NOT NULL,
`kek_label` VARCHAR(255) NOT NULL,
`status` VARCHAR(32) NOT NULL DEFAULT 'Active' COMMENT 'Active, Previous, Archived',
`hsm_profile_id` BIGINT UNSIGNED,
`hsm_key_label` VARCHAR(255),
`created` DATETIME NOT NULL,
`removed` DATETIME,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_uuid` (`uuid`),
UNIQUE KEY `uk_kms_key_version` (`kms_key_id`, `version_number`, `removed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
cloud.kms_wrapped_keyWrapped DEKs. Each volume references one wrapped key.
CREATE TABLE IF NOT EXISTS `cloud`.`kms_wrapped_key` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(40) NOT NULL,
`kms_key_id` BIGINT UNSIGNED,
`kek_version_id` BIGINT UNSIGNED,
`zone_id` BIGINT UNSIGNED NOT NULL,
`wrapped_blob` VARBINARY(4096) NOT NULL,
`created` DATETIME NOT NULL,
`removed` DATETIME,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_uuid` (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
cloud.kms_database_kek_objectsPKCS#11-compatible object storage for the database KMS provider. Key material is encrypted via DBEncryptionUtil (Base64-encoded KEK → DBEncryptionUtil.encrypt() → stored bytes).
CREATE TABLE IF NOT EXISTS `cloud`.`kms_database_kek_objects` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` VARCHAR(40) NOT NULL,
`object_class` VARCHAR(32) NOT NULL DEFAULT 'CKO_SECRET_KEY',
`label` VARCHAR(255) NOT NULL,
`object_id` VARBINARY(64),
`key_type` VARCHAR(32) NOT NULL DEFAULT 'CKK_AES',
`key_material` VARBINARY(512) NOT NULL COMMENT 'encrypted KEK material',
`is_sensitive` BOOLEAN NOT NULL DEFAULT TRUE,
`is_extractable` BOOLEAN NOT NULL DEFAULT FALSE,
`purpose` VARCHAR(32) NOT NULL,
`key_bits` INT NOT NULL,
`algorithm` VARCHAR(64) NOT NULL DEFAULT 'AES/GCM/NoPadding',
`created` DATETIME NOT NULL,
`removed` DATETIME,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_uuid` (`uuid`),
UNIQUE KEY `uk_label` (`label`, `removed`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
cloud.volumesTwo new columns added via schema migration:
-- KMS key reference
ALTER TABLE `cloud`.`volumes` ADD COLUMN `kms_key_id` BIGINT UNSIGNED;
ALTER TABLE `cloud`.`volumes` ADD CONSTRAINT `fk_volumes__kms_key_id`
FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`);
-- Wrapped DEK reference
ALTER TABLE `cloud`.`volumes` ADD COLUMN `kms_wrapped_key_id` BIGINT UNSIGNED;
ALTER TABLE `cloud`.`volumes` ADD CONSTRAINT `fk_volumes__kms_wrapped_key_id`
FOREIGN KEY (`kms_wrapped_key_id`) REFERENCES `kms_wrapped_key`(`id`);
A new KMS top-level menu section is added with two sub-sections:
kmskeyid)hsmprofileid)Key Storage: KEKs never leave the HSM (for PKCS#11 provider) or are stored encrypted in database (for database provider). DEKs are always stored wrapped.
Sensitive Data: HSM profile PINs/passwords encrypted via DBEncryptionUtil before storage. Encrypted values appear as ENC(...) in API responses.
Access Control: Only root admins can manage HSM profiles (create, update, delete). All users can list HSM profiles visible to them. KMS keys are account-scoped with domain hierarchy permissions.
Key Rotation: Background rewrap job gradually re-encrypts wrapped keys to avoid service disruption. Old KEK versions retained until all wrapped keys migrated. Cross-HSM migration supported.
PKCS#11 Cipher: AES-CBC with PKCS5Padding (FIPS-compliant, universal driver support). AES-GCM preferred but SunPKCS11 support is unreliable across drivers.
Error Handling: Per-operation timeout via Future.get(), retry with back-off for transient failures, fast-fail for non-retryable errors, graceful fallback to passphrase encryption.
Cluster Safety: GlobalLock("kms.rewrap.worker") ensures only one management server runs the rewrap job. Key rotation DB updates use Transaction.execute() for atomicity with compensating HSM cleanup on failure.
Usage Records: Track KMS key usage (wrap/unwrap operations, active wrapped key count) and emit usage events for billing integration with CloudStack's usage server
Scheduled Key Rotation Policies: Allow admins to define rotation policies (e.g., rotate every 90 days) with automatic KEK rotation and rewrap scheduling per KMS key
Managed HSM Offering: Provide HSM connectivity as a CloudStack service offering, allowing tenants to provision dedicated or shared HSM partitions (requires further investigation into HSM partitioning models and multi-tenancy isolation)
PKCS11HSMProvider (pending broader SunPKCS11 driver compatibility)