Majority of OCI customers use Vault Secrets service to store and access sensitive information such as passwords, private keys and more. Backing up these secrets is crucial to prevent data loss.
In this blog post, I will show you how to deploy a utility that backs up your secrets data to OCI Object Storage immutable bucket at specific intervals.
Why Back Up OCI Vault Secrets?
Backing up secrets is a crucial practice for several reasons:
- Accidental Deletion: Prevent data loss due to human error.
- Malicious Deletion: Mitigate risks associated with unauthorized actions.
- Disaster Recovery: Ensure you can restore secrets in case of system failures or other emergencies.
While backing up secrets is essential, it also introduces operational challenges, such as maintaining multiple sets of records and managing proper permissions to avoid accidental exposure of secrets.
Solution Overview
Step 1: Create an Object Storage Bucket where the secrets will be backedup
- Follow the steps from this page to create a new bucket in OCI Object Storage
- Ensure WORM Compliance for Objects in enabled. This will ensure, no malicious users can delete the files in the bucket.
Step 2: Create an OCI Function.
- Follow the instructions on this page to create an OCI Function
- Update the function with the provided code. Ensure you replace placeholders such as compartment_id, bucket_name and namespace with your actual values.
import oci
import json
from datetime import datetime
def list_secrets(compartment_id, vault_client, vault_id, secrets_client):
secrets_list = {}
try:
paginator = vault_client.list_secrets(compartment_id=compartment_id, vault_id=vault_id)
for page in paginator.data:
secret_info = {
"vault_id": page.vault_id,
"secret_name": page.secret_name,
"lifecycle_state": page.lifecycle_state,
"key_id": page.key_id,
"secret_ocid": page.id,
"freeform_tags": page.freeform_tags,
"description": page.description,
"time_of_deletion": page.time_of_deletion,
}
if page.lifecycle_state == 'ACTIVE':
secret_bundle_response = secrets_client.get_secret_bundle(secret_id=page.id, stage="LATEST")
secret_info["secret_bundle_content"] = secret_bundle_response.data.secret_bundle_content.content
secret_info["secret_bundle_content_type"] = secret_bundle_response.data.secret_bundle_content.content_type
secret_info["secret_stages"] = secret_bundle_response.data.stages
secret_info["version_number"] = secret_bundle_response.data.version_number
secrets_list[page.secret_name] = secret_info
except Exception as e:
print(f"Error listing secrets: {e}", flush=True)
return secrets_list
def serialize_data(data):
if isinstance(data, datetime):
return data.isoformat()
elif isinstance(data, dict):
return {key: serialize_data(value) for key, value in data.items()}
elif isinstance(data, list):
return [serialize_data(element) for element in data]
else:
return data
def upload_secret_to_object_storage(object_storage_client, namespace, bucket_name, folder_name, secret_name, secret_details):
try:
object_name = f"{folder_name}/{secret_name}.json"
serialized_secret_details = serialize_data(secret_details)
secret_json = json.dumps(serialized_secret_details)
object_storage_client.put_object(
namespace,
bucket_name,
object_name,
secret_json
)
print(f"Successfully uploaded {secret_name} to folder {folder_name} in bucket {bucket_name}.", flush=True)
except Exception as e:
print(f"Error uploading secret {secret_name} to Object Storage: {e}", flush=True)
def list_vaults(kms_vault_client, compartment_id):
vault_list = {}
try:
vault_response = kms_vault_client.list_vaults(compartment_id)
for vault in vault_response.data:
vault_list[vault.display_name] = {
"display_name": vault.display_name,
"freeform_tags": vault.freeform_tags,
"management_endpoint": vault.management_endpoint,
"vault_type": vault.vault_type,
"vault_id": vault.id,
"lifecycle_state": vault.lifecycle_state,
}
except Exception as e:
print(f"Error listing vaults: {e}", flush=True)
return vault_list
# Prompt user for runtime inputs
compartment_id = 'ocid1.compartment.oc1..xxx #input("Enter your compartment OCID: ")
bucket_name = 'backup-vault-secrets' #input("Enter your bucket name: ")
namespace = namespace #input("Enter your OCI namespace: ")
# Configure OCI clients
Signer = oci.auth.signers.get_resource_principals_signer() # Get Resource Principal Credentials
secrets_client = oci.secrets.SecretsClient(config={}, signer=Signer)
kms_vault_client = oci.key_management.KmsVaultClient(config={}, signer=Signer)
vault_client = oci.vault.VaultsClient(config={}, signer=Signer)
object_storage_client = oci.object_storage.ObjectStorageClient(config={}, signer=Signer)
iam_client = oci.identity.IdentityClient(config={}, signer=Signer)
compartment_name = iam_client.get_compartment(compartment_id).data.name
try:
response=object_storage_client.get_bucket(namespace, bucket_name)
bucket_exists=True
except Exception as e:
print(f"Bucket {bucket_name} doesnt exist. Create the bucket and ensure WORM feature is enabled", flush=True)
bucket_exists=False
if bucket_exists==True:
# Step 1: List vaults in the compartment
vaults = list_vaults(kms_vault_client, compartment_id)
# Step 2: Generate unique folder name using current time
current_time = datetime.now().strftime("%Y%m%d_%H%M%S")
folder_name = f"{compartment_name}-secrets_{current_time}"
# Step 3: Process each active vault
for vault_info in vaults.values():
if vault_info["lifecycle_state"] == 'ACTIVE':
print(f"Retrieving secrets from vault {vault_info['display_name']}", flush=True)
secrets = list_secrets(compartment_id, vault_client, vault_info["vault_id"], secrets_client)
for secret_name, secret_details in secrets.items():
upload_secret_to_object_storage(object_storage_client, namespace, bucket_name, folder_name, secret_name, secret_details)
else:
print(f"Bucket {bucket_name} doesnt exist. Create the bucket and ensure WORM feature is enabled", flush=True)
Note: Customers that prefer to run this script manually, update the config information and execute it in any python environment.
Step 3: Set up a Scheduler.
- Follow the instructions from this blog post to create a scheduler.
- Configure the scheduler to invoke the OCI Function at your desired intervals.
Step 4: Verification
- Ensure that the function runs as expected and backs up secrets to your specified Object Storage bucket.
In this blog post, we have demonstrated how to automate the backup of OCI Vault secrets using OCI Functions and Object Storage. This solution helps ensure that your sensitive data is secure and can be restored in case of accidental or malicious loss.
