X

Break New Ground

Oracle Functions: Using Key Management To Encrypt And Decrypt Configuration Variables

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. 

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:

  • Create a KMS vault
  • Create a Master Encryption Key
  • Generate a Data Encryption Key (DEK) from the Master Encryption Key
  • Use the DEK plaintext return value to encrypt the sensitive value (offline)
  • Store the encrypted sensitive value as a config variable in the serverless application
  • Store the DEK ciphertext and the initVector used to encrypt the sensitive value as Function config variables
  • Within the function, decrypt the DEK ciphertext back into plaintext using the OCID and Cryptographic Endpoint by invoking the OCI KMS SDK
  • Decrypt the sensitive value using the decrypted DEK plaintext and the initVector

The 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.

Before We Get Started

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 keys, vaults and key-delegate - again, refer to the documentation on setting policies to make sure you fully understand the policies that you are applying.

Create Application And Function

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

Dependencies

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 javax.activation-api:

Dockerfile

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!

Create Vault And Master Encryption Key

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 Data Encryption Key (DEK)

Create the Data Encryption Key (DEK) via the OCI CLI like so:

Keep the ciphertext and plaintext values returned from the generate-data-encryption-key call handy, we’ll need them in a minute.

Example DEK ciphertext:

I...[random chars]...​AAAAAA==

Store the ciphertext as a config var with the application:

fn config app fn-kms DEK_CIPHERTEXT I...[random chars]...​AAAAAA==

Example DEK plaintext:

0…​[random chars]...=

Encrypt Password

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

Serverless Function

We can now modify our serverless function to decrypt the encrypted password. Here's what that looks like:

Testing

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:

Deploying

To deploy the function, run:

fn deploy --app fn-kms

To invoke:

fn invoke fn-kms fn-kms-demo

Which will return the decrypted password:

{"decryptedPassword":"hunter2"}

Summary

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.

Additional Reading

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.

Photo by Silas Köhler on Unsplash

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.