Vault

The Oracle Cloud Infrastructure Vault is a service that provides management of encryption keys and secret credentials. A vault is a storage container that holds keys and secrets. The Vault service not only secures your secrets it provides a central repository that allows them to be used in different notebooks and shared with only those that need access. No longer will your secrets be stored in code that can accidentally be checked into git repositories.

This notebook demonstrates how to create a vault, a key, and store a secret that is encrypted with that key. It also demonstrates how to retrieve the secret so that it can be used in a notebook. The notebook explains how to update that secret and basic operations, such as listing deleting vaults, keys, and secrets.

This notebook performs CRUD (create, read, update, delete) operations on vaults, keys, and secrets. These are all part of the Vault Service. The account that is using this notebook requires permissions to these resources. The account administrator needs to grant privileges to perform these actions. How the permissions are configured can depend on your tenancy configuration, see the Vault Service’s permissions documentation for details. The Vault Service’s common policies are:

allow group <group> to manage vaults in compartment <compartment>
allow group <group> to manage keys in compartment <compartment>
allow group <group> to manage secret-family in compartment <compartment>

Introduction to the Vault Service

The Oracle Cloud Infrastructure Vault lets you centrally manage the encryption keys that protect your data and the secret credentials that you use to securely access resources.

Vaults securely store master encryption keys and secrets that you might otherwise store in configuration files or in code.

Use the Vault service to exercise control over the lifecycle keys and secrets. Integration with Oracle Cloud Infrastructure Identity and Access Management (IAM) lets you control who and what services can access which keys and secrets and what they can do with those resources. The Oracle Cloud Infrastructure Audit integration gives you a way to monitor key and secret use. Audit tracks administrative actions on vaults, keys, and secrets.

Keys are stored on highly available and durable hardware security modules (HSM) that meet Federal Information Processing Standards (FIPS) The main use case for a data scientist is to store a secret, such as an SSH key, database password, or some other credential. To do this, a vault and key are required. By default, this notebook creates these resources. However, the vault_id and key_id variables can be updated with vault and key OCIDs to use existing resources.

# Select the configuration file to connect to Oracle Cloud Infrastructure resources
config = from_file(path.join(path.expanduser("~"), ".oci", "config"), "DEFAULT")

# Select the compartment to create the secrets in.
# Use the notebook compartment by default
compartment_id = os.environ['NB_SESSION_COMPARTMENT_OCID']

# Enter a vault OCID. Otherwise, one is created.
vault_id = "<vault_id>"
# Enter a KMS OCID to encrypt the secret. Otherwise, one is created
key_id = "<key_id>"

For the purposes of this notebook, a secret is stored. The secret is the credentials needed to access a database. The notebook is designed so that any secret can be stored as long as it is in the form of a dictionary. To store your secret, just modify the dictionary.

# Sample credentials that are going to be stored.
credential = {'database_name': 'databaseName_high',
              'username': 'admin',
              'password': 'MySecretPassword',
              'database_type': 'oracle'}

Note, to connect to an Oracle database the database_name value should be its connection identifier. You can find the connection identifier by extracting the credential wallet zip file and opening the tnsnames.ora file (connection_identifier = (…)). Usually the connection identifier will end with _high, _medium or _low i.e. ‘MyDatabaseName_high’.

Create a Vault

To store a secret, a key is needed to encrypt and decrypt the secret. This key and secret are stored in a vault. The code in the following cell creates a vault if you have not specified an OCID in the vault_id variable. The KmsVaultClient class takes a configuration object and establishes a connection to the key management service (KMS). Communication with KmsVaultClient is asynchronous. For the purpose of this notebook, it is better to have synchronous communication so the KmsVaultClient are wrapped in a KmsVaultClientCompositeOperations object.

The details of the vault are specified using an object of the CreateVaultDetails type. A compartment ID must be provided along with the properties of the vault. For the purposes of this notebook, the vault’s display name is DataScienceVault_ and a random string because the names of a vault must be unique. This value can be changed to fit your individual needs.

if vault_id == "<vault_id>":
    # Create a VaultClientCompositeOperations for composite operations.
    vault_client = KmsVaultClientCompositeOperations(KmsVaultClient(config))

    # Create vault_details object for use in creating the vault.
    vault_details = CreateVaultDetails(compartment_id=compartment_id,
        vault_type=oci.key_management.models.Vault.VAULT_TYPE_DEFAULT,
        display_name="DataScienceVault_{}".format(str(uuid.uuid4())[-6:]))

    # Vault creation is asynchronous; Create the vault and wait until it becomes active.
    print("Creating vault...", end='')
    vault = vault_client.create_vault_and_wait_for_state(vault_details,
                wait_for_states=[oci.vault.models.Secret.LIFECYCLE_STATE_ACTIVE]).data
    vault_id = vault.id
    print('Done')
    print("Created vault: {}".format(vault_id))
else:
    # Get the vault using the vault OCID.
    vault = KmsVaultClient(config).get_vault(vault_id=vault_id).data
    print("Using vault: {}".format(vault.id))
Creating vault...Done
Created vault: ocid1.vault..<unique_ID>

Create a Key

The secret is encrypted and decrypted using an AES key. The code in the following cell creates a key if you have not specified an OCID in the key_id variable. The KmsManagementClient class takes a configuration object and the endpoint for the vault that is going to be used to store the key. It establishes a connection to the KMS. Communication with KmsManagementClient is asynchronous. For the purpose of this notebook, it is better to have synchronous communication so the KmsManagementClient is wrapped in a KmsManagementClientCompositeOperations object.

The details of the key are specified using an object of type CreateKeyDetails. A compartment OCID must be provided along with the properties of the key. The KeyShape class defines the properties of the key. In this example, it is a 32-bit AES key.

For the purposes of this notebook, the key’s display name is DataScienceKey_ and a random string because the names of a key must be unique. This value can be changed to fit your individual needs.

if key_id == "<key_id>":
    # Create a vault management client using the endpoint in the vault object.
    vault_management_client = KmsManagementClientCompositeOperations(
        KmsManagementClient(config, service_endpoint=vault.management_endpoint))

    # Create key_details object that needs to be passed when creating key.
    key_details = CreateKeyDetails(compartment_id=compartment_id,
        display_name="DataScienceKey_{}".format(str(uuid.uuid4())[-6:]),
        key_shape=KeyShape(algorithm="AES", length=32))

    # Vault creation is asynchronous; Create the vault and wait until it becomes active.
    print("Creating key...", end='')
    key = vault_management_client.create_key_and_wait_for_state(key_details,
              wait_for_states=[oci.key_management.models.Key.LIFECYCLE_STATE_ENABLED]).data
    key_id = key.id
    print('Done')
    print("Created key: {}".format(key_id))
else:
    print("Using key: {}".format(key_id))
Creating key...Done
Created key: ocid1.key..<unique_ID>

Secret

Store a Secret

The code in the following cell creates a secret that is to be stored. The variable credential is a dictionary and contains the information that is to be stored. The UDF dict_to_secret takes a Python dictionary, converts it to a JSON string, and then Base64 encodes it. This string is what is to be stored as a secret so the secret can be parsed by any system that may need it.

The VaultsClient class takes a configuration object and establishes a connection to the Vault service. Communication with VaultsClient is asynchronous. For the purpose of this notebook, it is better to have synchronous communication so VaultsClient is wrapped in a VaultsClientCompositeOperations object.

The contents of the secret are stored in a Base64SecretContentDetails object. This object contains information about the encoding being used, the stage to be used,and most importantly the payload (the secret). The CreateSecretDetails class is used to wrap the Base64SecretContentDetails object and also specify other properties about the secret. It requires the compartment OCID, the vault that is to store the secret, and the key to use to encrypt the secret. For the purposes of this notebook, the secret’s display name is DataScienceSecret_ and a random string because the names of a secret must be unique. This value can be changed to fit your individual needs.

# Encode the secret.
secret_content_details = Base64SecretContentDetails(
    content_type=oci.vault.models.SecretContentDetails.CONTENT_TYPE_BASE64,
    stage=oci.vault.models.SecretContentDetails.STAGE_CURRENT,
    content=dict_to_secret(credential))

# Bundle the secret and metadata about it.
secrets_details = CreateSecretDetails(
        compartment_id=compartment_id,
        description = "Data Science service test secret",
        secret_content=secret_content_details,
        secret_name="DataScienceSecret_{}".format(str(uuid.uuid4())[-6:]),
        vault_id=vault_id,
        key_id=key_id)

# Store secret and wait for the secret to become active.
print("Creating secret...", end='')
vaults_client_composite = VaultsClientCompositeOperations(VaultsClient(config))
secret = vaults_client_composite.create_secret_and_wait_for_state(
             create_secret_details=secrets_details,
             wait_for_states=[oci.vault.models.Secret.LIFECYCLE_STATE_ACTIVE]).data
secret_id = secret.id
print('Done')
print("Created secret: {}".format(secret_id))
Creating secret...Done
Created secret: ocid1.vaultsecret..<unique_ID>

Retrieve a Secret

The SecretsClient class takes a configuration object. The get_secret_budle method takes the secret’s OCID and returns a Response object. Its data attribute returns SecretBundle object. This has an attribute secret_bundle_content that has the object Base64SecretBundleContentDetails and the content attribute of this object has the actual secret. This returns the Base64 encoded JSON string that was created with the dict_to_secret function. The process can be reversed with the secret_to_dict function. This will return a dictionary with the secrets.

secret_bundle = SecretsClient(config).get_secret_bundle(secret_id)
secret_content = secret_to_dict(secret_bundle.data.secret_bundle_content.content)

print(secret_content)
{'database': 'datamart', 'username': 'admin', 'password': 'MySecretPassword'}

Update a Secret

Secrets are immutable but it is possible to update them by creating new versions. In the code in the following cell, the credential object updates the password key. To update the secret, a Base64SecretContentDetails object must be created. The process is the same as previously described in the Store a Secret section. However, instead of using a CreateSecretDetails object, an UpdateSecretDetails object is used and only the information that is being changed is passed in.

Note that the OCID of the secret does not change. A new secret version is created and the old secret is rotated out of use, but it may still be available depending on the tenancy configuration.

The code in the following cell updates the secret. It then prints the OCID of the old secret and the new secret (they will be the same). It also retrieves the updated secret, converts it into a dictionary, and prints it. This shows that the password was actually updated.

# Update the password in the secret.
credential['password'] = 'UpdatedPassword'

# Encode the secret.
secret_content_details = Base64SecretContentDetails(
    content_type=oci.vault.models.SecretContentDetails.CONTENT_TYPE_BASE64,
    stage=oci.vault.models.SecretContentDetails.STAGE_CURRENT,
    content=dict_to_secret(credential))

# Store the details to update.
secrets_details = UpdateSecretDetails(secret_content=secret_content_details)

#Create new secret version and wait for the new version to become active.
secret_update = vaults_client_composite.update_secret_and_wait_for_state(
    secret_id,
    secrets_details,
    wait_for_states=[oci.vault.models.Secret.LIFECYCLE_STATE_ACTIVE]).data

# The secret OCID does not change.
print("Orginal Secret OCID: {}".format(secret_id))
print("Updated Secret OCID: {}".format(secret_update.id))

### Read a secret's value.
secret_bundle = SecretsClient(config).get_secret_bundle(secret_update.id)
secret_content = secret_to_dict(secret_bundle.data.secret_bundle_content.content)

print(secret_content)
Orginal Secret OCID: ocid1.vaultsecret..<unique_ID>
Updated Secret OCID: ocid1.vaultsecret..<unique_ID>
{'database': 'datamart', 'username': 'admin', 'password': 'UpdatedPassword'}

List Resources

This section demonstrates how to obtain a list of resources from the vault, key, and secrets

List Secrets

The list_secrets method of the VaultsClient provides access to all secrets in a compartment. It provides access to all secrets that are in all vaults in a compartment. It returns a Response object and the data attribute in that object is a list of SecretSummary objects.

The SecretSummary class has the following attributes:

  • compartment_id: Compartment OCID.

  • defined_tags: Oracle defined tags.

  • description: Secret description.

  • freeform_tags: User-defined tags.

  • id: OCID of the secret.

  • key_id: OCID of the key used to encrypt and decrypt the secret.

  • lifecycle_details: Details about the lifecycle.

  • lifecycle_state: The current lifecycle state, such as ACTIVE and PENDING_DELETION.

  • secret_name: Name of the secret.

  • time_created: Timestamp of when the secret was created.

  • time_of_current_version_expiry: Timestamp of when the secret expires if it is set to expire.

  • time_of_deletion: Timestamp of when the secret is deleted if it is pending deletion.

  • vault_id: Vault OCID that the secret is in.

Note that the SecretSummary object does not contain the actual secret. It does provide the secret’s OCID that can be used to obtain the secret bundle, which has the secret. See the retrieving a secret, section.

The following code uses attributes about a secret to display basic information about all the secrets.

secrets = VaultsClient(config).list_secrets(compartment_id)
for secret in secrets.data:
    print("Name: {}\nLifecycle State: {}\nOCID: {}\n---".format(
        secret.secret_name, secret.lifecycle_state,secret.id))
Name: DataScienceSecret_fd63db
Lifecycle State: ACTIVE
OCID: ocid1.vaultsecret..<unique_ID>
---
Name: DataScienceSecret_fcacaa
Lifecycle State: ACTIVE
OCID: ocid1.vaultsecret..<unique_ID>
---

List Keys

The list_keys method of the KmsManagementClient object provide access returns a list of keys in a specific vault. It returns a Response object and the data attribute in that object is a list of KeySummary objects.

The KeySummary class has the following attributes:

  • compartment_id: OCID of the compartment that the key belongs to.

  • defined_tags: Oracle defined tags.

  • display_name: Name of the key.

  • freeform_tags: User-defined tags.

  • id: OCID of the key.

  • lifecycle_state: The lifecycle state such as ENABLED.

  • time_created: Timestamp of when the key was created.

  • vault_id: OCID of the vault that holds the key.

Note, the KeySummary object does not contain the AES key. When a secret is returned that was encrypted with a key it will automatiacally be decrypted. The most common use-case for a data scientist is to list keys to get the OCID of a desired key but not to interact directly with the key.

The following code uses some of the above attributes to provide details on the keys in a given vault.

# Get a list of keys and print some information about each one
key_list = KmsManagementClient(config, service_endpoint=vault.management_endpoint).list_keys(
               compartment_id=compartment_id).data
for key in key_list:
    print("Name: {}\nLifecycle State: {}\nOCID: {}\n---".format(
        key.display_name, key.lifecycle_state,key.id))
Name: DataScienceKey_1ddde6
Lifecycle State: ENABLED
OCID: ocid1.key..<unique_ID>
---

List Vaults

The list_vaults method of the KmsVaultClient object returns a list of all the vaults in a specific compartment. It returns a Response object and the data attribute in that object is a list of VaultSummary objects.

The VaultSummary class has the following attributes:

  • compartment_id: OCID of the compartment that the key belongs to.

  • crypto_endpoint: The end-point for encryption and decryption.

  • defined_tags: Oracle defined tags.

  • display_name: Name of the key.

  • freeform_tags: User-defined tags.

  • id: OCID of the vault.

  • lifecycle_state: The lifecycle state, such as ACTIVE.

  • time_created: Timestamp of when the key was created.

  • management_endpoint: Endpoint for managing the vault.

  • vault_type: The oci.key_management.models.Vault type. For example, DEFAULT.

The following code uses some of the above attributes to provide details on the vaults in a given compartment.

# Get a list of vaults and print some information about each one.
vault_list = KmsVaultClient(config).list_vaults(compartment_id=compartment_id).data
for vault_key in vault_list:
    print("Name: {}\nLifecycle State: {}\nOCID: {}\n---".format(
        vault_key.display_name, vault_key.lifecycle_state,vault_key.id))
Name: DataScienceVault_594c0f
Lifecycle State: ACTIVE
OCID: ocid1.vault..<unique_ID>
---
Name: DataScienceVault_a10ee1
Lifecycle State: DELETED
OCID: ocid1.vault..<unique_ID>
---
Name: DataScienceVault_0cbf46
Lifecycle State: ACTIVE
OCID: ocid1.vault..<unique_ID>
---
Name: shay_test
Lifecycle State: ACTIVE
OCID: ocid1.vault..<unique_ID>
---

Deletion

Vaults, keys, and secrets cannot be deleted immediately. They are marked as pending deletion. By default, they are deleted 30 days after they request for deletion. The length of time before deletion is configurable.

Delete a Secret

The schedule_secret_deletion method of the VaultsClient class is used to delete a secret. It requires the secret’s OCID and a ScheduleSecretDeletionDetails object. The ScheduleSecretDeletionDetails provides details about when the secret is deleted.

The schedule_secret_deletion method returns a Response object that has information about the deletion process. If the key has already been marked for deletion, a ServiceError occurs with information about the key.

VaultsClient(config).schedule_secret_deletion(secret_id, ScheduleSecretDeletionDetails())

Delete a Key

The schedule_key_deletion method of the KmsManagementClient class is used to delete a key. It requires the key’s OCID and a ScheduleKeyDeletionDetails object. The ScheduleKeyDeletionDetails provides details about when the key is deleted.

The schedule_key_deletion method returns a Response object that has information about the deletion process. If the key has already been marked for deletion, a ServiceError occurs.

Note that secrets are encrypted with a key. If that key is deleted, then the secret cannot be decrypted.

KmsManagementClient(config, service_endpoint=vault.management_endpoint).schedule_key_deletion(key_id, ScheduleKeyDeletionDetails())

Delete a Vault

The schedule_vault_deletion method of the KmsVaultClient class is used to delete a vault. It requires the vault’s OCID and a ScheduleVaultDeletionDetails object. The ScheduleVaultDeletionDetails provides details about when the vault is deleted.

The schedule_vault_deletion method returns a Response object that has information about the deletion process. If the vault has already been marked for deletion, then a ServiceError occurs.

Note that keys and secrets are associated with vaults. If a vault is deleted, then all the keys and secrets in that vault are deleted.

try:

Note, the KeySummary object does not contain the AES key. When a secret is returned that was encrypted with a key it will automatiacally be decrypted. The most common use-case for a data scientist is to list keys to get the OCID of a desired key but not to interact directly with the key.

The following code uses some of the above attributes to provide details on the keys in a given vault.

# Get a list of keys and print some information about each one
key_list = KmsManagementClient(config, service_endpoint=vault.management_endpoint).list_keys(
               compartment_id=compartment_id).data
for key in key_list:
    print("Name: {}\nLifecycle State: {}\nOCID: {}\n---".format(
        key.display_name, key.lifecycle_state,key.id))