I've covered quite a few different topics related to Oracle Functions recently on this blog, but today I'll cover what probably should have been the first post in this series. In my previous posts, I showed you how to set configuration variables for your applications and functions, but I have yet to show you how to keep those variables secure. In this post, we'll look at using Key Management in your Oracle Cloud tenancy to encrypt and decrypt your configuration to do just that.
Note: The solution presented in this post is valid, but there is now a much easier way to store sensitive data in the Oracle Cloud. For information on that method, see https://blogs.oracle.com/developers/protect-your-sensitive-data-with-secrets-in-the-oracle-cloud
Since this process involves multiple steps, I thought it would be helpful to give you an outline of the steps that we're going to take:
plaintextreturn value to encrypt the
sensitive valueas a config variable in the serverless application
initVectorused to encrypt the
sensitive valueas Function config variables
plaintextusing the OCID and Cryptographic Endpoint by invoking the OCI KMS SDK
sensitive valueusing the decrypted DEK
sensitive value referred to in the outline above can be anything that you need to be encrypted. Database passwords, API keys, etc - it doesn't matter what it is, it's just a placeholder that refers to any value that needs to be stored encrypted and not in plain text in an Oracle Function configuration variable.
I know it sounds like a lot of steps, but it's really not as hard as it might sound and these steps won't take you long to complete if you follow along. And, let's be honest, security is the most important thing when it comes to our applications and functions.
We’re going to utilize resource principals within our Oracle Function. This means that we won’t have to include OCI configuration files to use the OCI SDK, but does require that we have created a dynamic group within the tenancy which has the proper policies to allow us to interact with the KMS API.
First, create a 'Dynamic Group' and set the 'rules' similarly to this (use the OCID of the compartment where you are deploying your functions):
You can read more about dynamic groups and using Resource Principals with Oracle Functions in the documentation. The next step is to create a policy for the dynamic group that allows the group to manage
key-delegate - again, refer to the documentation on setting policies to make sure you fully understand the policies that you are applying.
Now, create a serverless application (substitute a valid subnet OCID):
fn create app --annotation oracle.com/oci/subnetIds='["ocid1.subnet.oc1.phx..."]' fn-kms
And a function:
fn init --runtime java fn-kms-demo
We'll need to add a dependency to the
pom.xml file for the KMS SDK:
If you’re using Java 11, manually include the
We'll need to create our own
Dockerfile because we're depending on environment variables. The only difference in this
Dockerfile from what is executed by default when deploying a function is that we're skipping running our tests during the Docker build. We have to do this because there is currently no way to pass environment variables to the Docker build context when deploying our function. You can use the example below:
Be sure to manually run your tests before deploying your function!
In the OCI console sidebar, under 'Governance and Administration', select 'Security' → 'Key Management':
Click on 'Create Vault' and enter the vault details:
After the vault is created, click on the vault name to view the vault details. Within the vault details, click on 'Create Key' to create a new Master Encryption Key and populate the dialog:
Copy the OCID of the Master Encryption Key and the 'Cryptographic Endpoint' from the vault. We’ll use this to create a Data Encryption Key (DEK) for our DB password.
Create the Data Encryption Key (DEK) via the OCI CLI like so:
plaintext values returned from the
generate-data-encryption-key call handy, we’ll need them in a minute.
Store the ciphertext as a config var with the application:
fn config app fn-kms DEK_CIPHERTEXT I...[random chars]...AAAAAA==
In this step we’re encrypting the password offline, not within the function. The function will decrypt the value later on when it’s running. Encrypt the password using the DEK in a standalone Java program. Below is a sample that you could potentially use.
Note: Plug in your DEK
plaintext value and choose a random 16 byte string for the
initVector. We’ll store the
initVector as a config var so we can use it when decrypting later on.
Store the random 16 byte
initVector string as a config var with the application:
fn config app fn-kms INIT_VECTOR_STRING [Random 16 byte string]
Copy the output of the above program. This is our encrypted password. Set this as a config var in the application:
fn config app fn-kms ENCRYPTED_PASSWORD N...==
Finally, set the Master Encryption Key OCID and the Cryptographic Endpoint as config vars for the application:
fn config app fn-kms KEY_OCID ocid1.key.oc1.phx...
fn config app fn-kms ENDPOINT https://...-crypto.kms.us-phoenix-1.oraclecloud.com
We can now modify our serverless function to decrypt the encrypted password. Here's what that looks like:
As stated above, you'll need to manually test the function.
Before you can test this function locally, you’ll seed to set some environment variables. See
env.sh in the root of the GitHub project for the variables that need to be set (or copy from below). All of these values are obtained by following the steps above (note they all match up to the config vars you have already set for the application).
After setting the necessary environment variables, write a unit test:
To deploy the function, run:
fn deploy --app fn-kms
fn invoke fn-kms fn-kms-demo
Which will return the decrypted password:
In this post, we used OCI KMS to create a vault and a Master Encryption Key. We used that Master Encryption Key to create a Data Encryption Key (DEK) and used that DEK to encrypt our sensitive value and then stored that encrypted sensitive value in our serverless function's configuration. Then we accessed the encrypted sensitive value from our deployed function and decrypted it so that it could be used in our function.
Feel free to check out my other posts on Oracle Functions:
The code used in this demo is available for your reference on GitHub at https://github.com/recursivecodes/fn-kms-demo.