Imagine a Magician that can read your mind and suddenly pulls a secret out of your head you never told them. This is kind of what I am going show how to do, but using OCI magic. Take the following use case.
In point 2 above the username is not necessarily a secret, but the password is. One option is too hard code the password in the program, not an uncommon approach. But is that really a good idea? I think or at least hope we can all agree the answer is — NO!! In this article I will show an alternative option to store a password or any other secret in a secure way by leveraging a new OCI feature called Instance Principals, and in combination with another new feature called the Vault we have a great way to solve this problem without a lot of effort. Interested? Let's go!
The Instance Principals is something unique to OCI where the compute instance itself can be authorized to make API calls to other services. No password needed too retrieve the secret! It's the magic of the Instance Principals at work.
Kiran Thakkar’s wrote a blog Secure way of managing secrets in OCI that covers concepts around the Vault that includes some bonus Python examples which one shows how to retrieve a secret.
To add even more control, we can use a policy that can restrict a program running on a compute instance to only retrieve a specific secret from a Vault. By combining all three features, Instance Principals, Vault, and policy, we have a much better approach to allowing a program to access sensitive information. So let's do it!
The following summary lists what is need to make all this work. Follow each section in the order you see it to be successful. The effort will be worth it.
A compartment is a logical container to organize and control access to OCI resources. In our case we need a compartment for our Compute instance Vault. If you already have a Compartment feel free to skip to the next section. Use the following steps to create a compartment.
A Dynamic Group is a group that dynamically grant access to resources based on a rule. Our Dynamic Group will be used with a matching rule to determine which instances we want to allow API calls against the service we are going to use. The following rule is an example, but could easily be modified to meet other requirements. Use the following steps to create a dynamic group.
ALERT: Use alternative rules found in Managing Dynamic Groups to provide even greater restrictions to resources if needed. |
We will now create a Vault in the compartment created earlier, then add a key that will be used to encrypt a new secret. The secret could be anything, but for our example we will store a password. Note that you could add multiple secrets if needed. Using the following steps to create a vault, a key, and a secret.
A policy provides a way to control access to resources. In the OCI documentation in section Creating a Dynamic Group and Matching rules, it gives an example that is really meant to allow administrators to manage Vaults, Keys, and Secrets, which grants a lot of control; i.e.
NOTE: POLICY FOR ADMINISTRATORS
allow dynamic-group my-group to manage secret-family in tenancy
allow dynamic-group my-group to manage vault in tenancy
allow dynamic-group my-group to manage keys in tenancy
However, I am going to rein back the above statement rules to restrict our Python script (created later) running on an instance to only be allowed to retrieve a specific secret using a single statement with a combination of Verbs, Resource-Types, and General Variables. Now compare my example:
NOTE: BETTER POLICY TO ONLY ACCESS A SECRET
allow dynamic-group my-secret-group to read secret-family in compartment my-compartment where target.secret.name = 'my-secret'
If we decompose the above single statement we can see how it provides stricter access to our secret which you can appreciate.
allow dynamic-group my-secret-group
to read secret-family
in compartment my-compartment
where target.secret.name = 'my-secret'
Feel free to use combinations of Dynamic Groups, Verbs, Resource-Types, and General Variables to build your own restrictions. Use the following steps to create a the example policy.
In the last two sections we need to install some Linux packages and then create a script, but before we do we need a Compute instance. If you already have a Compute instance you can skip this step. If you need to create a Compute instance these are very basic steps to create a Linux instance. Use the following steps to create a compute instance if needed.
With the compute instance created previously or if you already had a compute instance, ssh into the instance as opc. Use the next three sections to install Python 3.6, pip 20.0.2, and oci-cli 2.10.0 or later.
By default, the OCI Linux 6 or 7 images currently at the time of this post include Python 2.7.5, but to work with the newer secret features we need minimally 2.7.9. If we follow the latest requirements of the OCI CLI SDK we should be using Python 3.5 or greater https://docs.cloud.oracle.com/en-us/iaas/Content/API/Concepts/cliconcepts.htm#Requirements. The following approach to installing a newer version of Python allows us to maintain any older version of Python yet still let us run a newer version for our purposes. Python 3.6 will be installed in its own directory where it can be referenced as needed.
python --version
sudo yum install -y rh-python36
scl enable rh-python36 bash
python --version
vim ~/.bashrc
# Added to source Python 3 to login environment
source scl_source enable rh-python36
By default, Python comes with pip 9.0.1, but we need version 20.0.2.
pip --version
pip install --user --upgrade pip
python -m pip -- version
This will install the latest oci cli. At the publishing of this article 2.10.0 was the latest.
python -m pip install oci-cli --upgrade
oci --version
Finally, we can create a script to retrieve our secret. The following steps creates a Python script that you can use as a framework to build on, but this could also be done in other languages that are supported such as Java, Ruby, and Go — Software Development Kits and Command Line Interface. Use the following steps to create a Python script with the given example.
vim get-secret.py
#!/usr/bin/env python3
# coding: utf-8
# COPYRIGHT (c) 2020 ORACLE A-TEAM
# THIS SAMPLE CODE IS PROVIDED FOR EDUCATIONAL PURPOSES OR
# TO ASSIST YOUR DEVELOPMENT OR ADMINISTRATION EFFORTS AND
# PROVIDED "AS IS" AND IS NOT SUPPORTED BY ORACLE CORPORATION.
# License: http://www.apache.org/licenses/LICENSE-2.0.html
import oci
import base64
import sys
# Replace secret_id value below with the ocid of your secret
secret_id = "ocid1.vaultsecret.oc1.<my_secret_ocid>"
# By default this will hit the auth service in the region the instance is running.
signer = oci.auth.signers.InstancePrincipalsSecurityTokenSigner()
# Get instance principal context
secret_client = oci.secrets.SecretsClient(config={}, signer=signer)
# Retrieve secret
def read_secret_value(secret_client, 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
# Print secret
secret_contents = read_secret_value(secret_client, secret_id)
print(format(secret_contents))
secret_id = "ocid1.vaultsecret.oc1.<my_secret_ocid>"
chmod +x get-secret.py
Testing the script is pretty simple. If it does not work go back and make sure each step was followed carefully.
python --version
./get-secret.py
<my secret here>
In summary there are several use cases where this method can be used. Manasi Vaishampayan wrote a great blog Going beyond TCP healthchecks with OCI Load Balancer that uses a program to access an external service, which uses a username and password.
What is used in this article could be leveraged to secure the password in a Vault. The benefit are the Instance Principal and Vault helps abstracts the complexity of building your own security approach to store and encrypt things like passwords or other sensitive information. Let OCI store the sensitive data in an encrypted Vault and then retrieved seamlessly using the magic of the Instance Principal and then throw in a tightly controlled policy to boot. If that were not secure enough, OCI also rotates the certificates in the Vault used to encrypt the data several times a day. If you want more even more, the Vault secret adds additional controls called Secret Rules that can for example expire a secret; see more on that here in Rules for Secrets.
I hope this article is useful and to learn more on both of these features please see more about Instance Principals in Calling Services from an Instance and the Overview of Vault.
I started with Oracle in 2005 and been a member of the Oracle A-Team since 2012 though have worked in Identity and Access Management since 1999. My journey with security continues the cloud that heavily includes Oracle Infrastructure Cloud (OCI). I enjoy writing articles built on real life use cases to help in areas where a standard document may not provide. I am a strong believer in learning by example to which I try to incorporate as many helpful tips, excellent diagrams, and instructional steps as I can.