Secure way of managing secrets in OCI

March 31, 2020 | 4 minute read
Kiran Thakkar
Consulting Solutions Architect
Text Size 100%:

Overview

Oracle has never been more committed to customer success. To enable customers' success, we are releasing more services and features every day. Secrets management is the new feather in the cap. As part of the Oracle Cloud Infrastructure Vault, we can now manage both secrets and keys.

Secrets are credentials such as passwords, certificates, SSH keys, or authentication tokens for third-party cloud services that you use with Oracle Cloud Infrastructure services. Storing secrets in a vault provides greater security than you might achieve by storing them elsewhere, such as in code or configuration files. You can retrieve secrets from the vault when you need them to access resources or other services. You (an application) can cache a secret and use it as long as you need it. 

All the tools and methods that you use with other Oracle Cloud Infrastructure services, like cli, API, Java SDK, Python SDK, and of course, console access, will work with Oracle Cloud Infrastructure vault as well. There is a lot of information about Oracle Cloud Infrastructure vault that you can read from here. I will focus on secrets use cases and talk about how to use them.

High-Level Secrets Use case

The following diagram illustrates the most fundamental secrets use case. You create secret (credentials) and store them in Oracle Cloud Infrastructure vault. The application can use/read the secret as needed and then connect to the target service. 

  1. An administrator creates credentials in the target system. The target system can be a database system or IDM system where you generate client credentials or system user account.
  2. Secrets management is a sub-component of Oracle Cloud Infrastructure vault. You have to create a vault and key first before you can create a secret. Secrets are stored in a vault encrypted using the key that you choose while creating a secret. Step 2 is to create a secret to store target system credentials, as shown in the diagram. 
  3. The third step is to provide a secret ID (secret OCID) to the application that needs to use the secret to connect to the target system.
  4. Whenever the application needs to use the target system credentials, it can connect to the vault to fetch the secret value. The app can choose to (I strongly recommend that they should) cache secrets value and use it as and when it needs it.
  5. The application uses secret value/credentials to connect to the target service. 

Advantages of secrets in Oracle Cloud Infrastructure vault

  • You can centralize secrets management and only administrators will have Create, Update, and Delete permissions on secrets
  • You can rotate/update secrets/credentials without any changes in the consumer application
  • Secrets are encrypted at rest to improve security posture
  • secrets management proliferates machine to machine communication or serverless computing by making it secure

Python SDK

In the references, I have added links to documents that talk about creating a secret from Oracle Cloud Infrastructure console, so I will not repeat it here. I will go over how to use the Python SDK to both manage secrets and use secrets in your application. You have to use vaults client to perform CRUD operation on secrets. However, applications should use secrets clients to read secrets. Using secrets client, you cannot perform Create, Update, and Delete operations.

Before you create a secret, you have to create a vault and a key that Oracle Cloud Infrastructure will use to encrypt secrets. I will not get into creating a vault and key. You can refer to kms example for creating vault and key. You will need vault OCID and key OCID to create a secret.

Create an instance of Vault client

config = config = oci.config.from_file(
        "~/.oci/config",
        "$OCI_PROFILE")  #Replace $OCI_PROFILE with the profile name to use
#Vault client to manage secrets
vaults_client = oci.vault.VaultsClient(config)
vaults_client_composite = oci.vault.VaultsClientCompositeOperations(vaults_client)

Create a secret

def create_secret(vaults_client_composite, compartment_id, secret_content, secret_name, valult_id, key_id):
    print("Creating a secret {}.".format(secret_name))
    
    # Create secret_content_details that needs to be passed when creating secret.
    secret_description = "This is just a test"
    secret_content_details = oci.vault.models.Base64SecretContentDetails(content_type=oci.vault.models.SecretContentDetails.CONTENT_TYPE_BASE64,
                                                               name=secret_content,
                                                               stage="CURRENT",
                                                               content=secret_content)
    secrets_details = oci.vault.models.CreateSecretDetails(compartment_id=compartment_id,
                                                           description = secret_description, 
                                                       secret_content=secret_content_details,
                                                       secret_name=secret_name,
                                                       vault_id=vault_id,
                                                       key_id=key_id)

    #Create secret and wait for the secret to become active
    response = vaults_client_composite.create_secret_and_wait_for_state(create_secret_details=secrets_details,
                                                                        wait_for_states=[
                                                                    oci.vault.models.Secret.LIFECYCLE_STATE_ACTIVE])
    return response

Create a new version of the secret

def create_newsecret_version(vaults_client_composite, secret_content, secret_id):
    print("Creating a new secret version {}.".format(secret_id))
    
    #Create secret_content_details that needs to be passed when updating secret content.
    secret_content_details = oci.vault.models.Base64SecretContentDetails(content_type=oci.vault.models.SecretContentDetails.CONTENT_TYPE_BASE64,
                                                               stage="CURRENT",
                                                               content=secret_content)
    
    secrets_details = oci.vault.models.UpdateSecretDetails(secret_content=secret_content_details)
    
    #Create new secret version and wait for the new version to become active.
    response = vaults_client_composite.update_secret_and_wait_for_state(secret_id, 
                                                                        secrets_details,
                                                                        wait_for_states=[
                                                                    oci.vault.models.Secret.LIFECYCLE_STATE_ACTIVE])
    return response

Move the secret to a different compartment

def move_secret(vaults_client, secret_id, target_compartment_id):
    print("Moving secret to a target compartment")
    
    #Create an object of Change Secret Compartment Details
    target_compartment_details = oci.vault.models.ChangeSecretCompartmentDetails(compartment_id=target_compartment_id)
    
    #Move the secret to target compartment and then wait for the state to become active.
    response = vaults_client.change_secret_compartment(secret_id, change_secret_compartment_details=target_compartment_details)
    target_state = oci.vault.models.Secret.LIFECYCLE_STATE_ACTIVE.lower()
    try:
        waiter_result = oci.wait_until(
            vaults_client, 
            vaults_client.get_secret(secret_id),
            evaluate_response=lambda r: getattr(r.data, 'lifecycle_state') and getattr(r.data, 'lifecycle_state').lower() == target_state,
            waiter_kwargs={}
        )
        result_to_return = waiter_result
        print("Change compartment response is {}.".format(result_to_return.data))
        return result_to_return
    except Exception as e:
        raise oci.exceptions.CompositeOperationError(partial_results=[response], cause=e)

Delete a secret

def delete_secret(vaults_client, secret_id, deletion_time):
    print("Deleting a secret")
    
    #Create Secret deletion details object.
    secret_deletion_details = oci.vault.models.ScheduleSecretDeletionDetails(time_of_deletion=deletion_time)
    
    #Delete the secret or mark the secret for deletion
    response = vaults_client.schedule_secret_deletion(secret_id, secret_deletion_details)
    print("Secret deletion response is: {}.".format(response.data))

Using secrets client to read a secret value

# Usage : python secret_examples.py secret_id
def read_secret_value(secret_client, secret_id):
    print("Reading vaule of secret_id {}.".format(secret_id))
    
    response = secret_client.get_secret_bundle(secret_id)
    
    base64_Secret_content = response.data.secret_bundle_content.content
    base64_secret_bytes = base64_Secret_content.encode('ascii')
    base64_message_bytes = base64.b64decode(base64_secret_bytes)
    secret_content = base64_message_bytes.decode('ascii')
    
    return secret_content
    
config = config = oci.config.from_file(
        "~/.oci/config",
        "$OCI_PROFILE") #Replace $OCI_PROFILE with the profile name to use

if len(sys.argv) != 2:
    raise RuntimeError(
        'This example expects an ocid for the secret to read.')

secret_id = sys.argv[1]

secret_client = oci.secrets.SecretsClient(config)
secret_content = read_secret_value(secret_client, secret_id)
print("Decoded content of the secret is: {}.".format(secret_content))

References

 

Kiran Thakkar

Consulting Solutions Architect

Kiran Thakkar is an expert in Identity and Access Management with more than 10 years of experience in the space. He is also OCI certified Associate Architect and help customers on OCI use cases. He is believer in blockchain technology and follows that space as it grows.


Previous Post

Options for migrating clean data to Fusion Applications Cloud

Bala Mahalingam | 6 min read

Next Post


Deploying Oracle Analytics Cloud Remote Data Gateway using Local Peering Gateways

Dayne Carley | 6 min read