Periodically rotating encryption keys is a recommended security practice and is required by some industry standards for compliance reasons. For scalability and manageability, enterprises often leverage the same symmetric Customer Managed Key (CMK) to encrypt multiple volumes that belong to the same group, such as using same key for the Development and Testing environments.

In Oracle Cloud Infrastructure (OCI), rotating a volume CMK is a multi-step process. This involves identifying each volume within a tenancy and following the prescribed procedure to rotate the key on a volume. Manually executing this process can be time-consuming and prone to errors, especially in environments with a large number of volumes.

To address this challenge, we have created a Python script that can be executed on-demand to automate the process of identifying volumes and rotating their CMK in OCI Vault. This script simplifies the key rotation process, ensuring that all volumes are properly updated with minimal manual intervention.

How the Script Works

The Python script performs the following tasks:

1.    Key Input: You decide which CMK in OCI Vault to rotate and provide the Key OCID as input to the script.
2.    Volume Identification: The script queries the tenancy (if needed, the script can be modified to only query specific compartments) to identify all volumes encrypted with the specified CMK.
3.    Key Rotation: Script creates a new version of the key in OCI Vault
4.    For each identified volume, the script initiates the key rotation process.
5.    Logging: The script logs the status of each key rotation, providing a clear audit trail for compliance purposes.


Implementation Steps

Prerequisites: Ensure that you have the necessary permissions to manage KMS keys and volumes in OCI.
Script Configuration: Configure the script with your CMK details and Log location.
Execution: Run the script to initiate the automated key rotation process.
Verification: Review the script logs to verify that all volumes have been successfully updated.

 

import oci
import time
import json
import csv
import sys
import logging

# Configure logging
logging.basicConfig(filename='key_update.log', level=logging.INFO, 
                    format='%(asctime)s - %(levelname)s - %(message)s')

config = oci.config.from_file()
# Enter the OCI KMS Key OCID 
key_ocid = 'ocid1.key.oc1.iad.aaaaa.dfsfsfsdfsdf'

# Initialize clients
kms_vault_client = oci.key_management.KmsVaultClient(config)
blockstorage_client = oci.core.BlockstorageClient(config)
resource_search_client = oci.resource_search.ResourceSearchClient(config)
identity_client = oci.identity.IdentityClient(config)

def get_volume_info(volume_summary):
    if volume_summary.resource_type == 'BootVolume':
        volume_dtls = blockstorage_client.get_boot_volume(volume_summary.identifier)
    elif volume_summary.resource_type == 'Volume':
        volume_dtls = blockstorage_client.get_volume(volume_summary.identifier)
    else:
        raise ValueError(f"Unknown resource type: {volume_summary.resource_type}")
    return volume_dtls.data

def update_kms_key(kms_key_id):
    structured_search = oci.resource_search.models.StructuredSearchDetails(
        query=f"query key resources return allAdditionalFields where identifier = '{kms_key_id}'",
        type="Structured",
        matching_context_type=oci.resource_search.models.SearchDetails.MATCHING_CONTEXT_TYPE_NONE
    )
    kms_search_response = resource_search_client.search_resources(structured_search).data
    vault_id = kms_search_response.items[0].additional_details['vaultId']
    current_key_version = kms_search_response.items[0].additional_details['currentKeyVersion']
    vault_resp = kms_vault_client.get_vault(vault_id=vault_id).data
    vault_management_endpoint = vault_resp.management_endpoint
    kms_management_client = oci.key_management.KmsManagementClient(config, service_endpoint=vault_management_endpoint)
    key_info = kms_management_client.get_key(kms_key_id).data
    new_key_version = kms_management_client.create_key_version(kms_key_id).data
    
    # Logging and writing to a file
    message = f"New key version created for key {key_info.display_name} in vault {vault_resp.display_name}"
    logging.info(message)
    print(message)
    return key_info

def update_volume_key(volume_id, kms_key_id, resource_type):
    if resource_type == 'BootVolume':
        blockstorage_client.update_boot_volume_kms_key(volume_id, 
            update_boot_volume_kms_key_details=oci.core.models.UpdateBootVolumeKmsKeyDetails(kms_key_id=kms_key_id))
    elif resource_type == 'Volume':
        blockstorage_client.update_volume_kms_key(volume_id, 
            update_volume_kms_key_details=oci.core.models.UpdateVolumeKmsKeyDetails(kms_key_id=kms_key_id))
    else:
        raise ValueError(f"Unknown resource type: {resource_type}")
    
    while True:
        if resource_type == 'BootVolume':
            get_volume_response = blockstorage_client.get_boot_volume(volume_id).data
        elif resource_type == 'Volume':
            get_volume_response = blockstorage_client.get_volume(volume_id).data
        
        if get_volume_response.lifecycle_state == oci.core.models.Volume.LIFECYCLE_STATE_AVAILABLE:
            break
    message = f"Volume is updated with the key"
    logging.info(message)
    print(message)

    
# Create a structured search query and loop over all block volumes
structured_search = oci.resource_search.models.StructuredSearchDetails(
    query="query bootvolume,volume resources",
    type="Structured",
    matching_context_type=oci.resource_search.models.SearchDetails.MATCHING_CONTEXT_TYPE_NONE
)
search_response = resource_search_client.search_resources(structured_search)

#Rotate KMS key/create new version of key
key_info = update_kms_key(key_ocid)

#loop over each volume metadata and do a string match on KMS key id
for resource_summary in search_response.data.items:
    volume_info = get_volume_info(resource_summary)
    if volume_info.kms_key_id == key_ocid:
        compartment_name = identity_client.get_compartment(volume_info.compartment_id).data.name
        kms_key_id = volume_info.kms_key_id
        volume_id = volume_info.id
        resource_type = resource_summary.resource_type
        message=f"Updating {resource_type} {resource_summary.display_name} ({volume_id}) in {compartment_name} compartment with Key {key_info.display_name}({key_ocid})"
        logging.info(message)
        print(message)
        update_volume_key(volume_id, kms_key_id, resource_type)
        print('-' * 30)

 

By automating the KMS key rotation process, you can enhance the security of your OCI environment while ensuring compliance with industry standards. This approach not only saves time but also reduces the risk of errors, making it an essential practice for enterprises managing large-scale cloud environments.