Berglas with Node.js on Cloud Run
By: James Hegedus
Tags: Berglas, Secrets, Google Cloud Run, Google Cloud Platform, Docker

Berglas with Node.js on Cloud Run

💻 Runtime Secret Decryption with Golang & Node.js

Google’s KMS provides a mechanism to encrypt secrets, but unfortunately does not provide a secure store service. Berglas is a Golang tool built to solve this problem. It utilises KMS to encrypt your secrets, store them in a secure GCS Bucket and decrypt them at runtime.

Being written in Golang, Berglas can be used in the Cloud Function Go runtime, but not the Node.js, Python or Java runtimes. Porting it to other languages would be costly and fragment the effort to keep the tool exploit-free and our secrets safe.

Now that we have serverless containers (Cloud Run), we can install whichever binaries we want, no longer are we restricted by the tools of our runtime’s ecosystem or the compute image supplied by our cloud provider. The following is an example showing Berglas with a Node.js docker image in the Cloud Run service!

Berglas with Node.js on Cloud Run

Berglas

Berglas has three main components: bootstrap, encrypt and runtime decryption. The Bootstrap and Encrypt steps are most easily executed within the GCP Cloud Shell. Berglas is supported in many more envs than Cloud Run, see the well maintained documentation after this quick introduction!

Bootstrap

This step provisions the resources required by Berglas:

configures a GCS Bucket (with location) to hold your secrets

configures a KMS keyring to be used to encrypt your secrets

configures audit logging to the Storage and KMS APIs

This step is intended to be run once per Bucket you wish to store secrets within. Since the Bucket uses Object level access control, we will do this once for this example. Depending on your security needs, you can either use the Object path for namespacing or multiple buckets.

Berglas Bootstrap

Encrypt

This step is run once per secret/key you wish to store:

create a secret in the storage location enrypted with the KMS Key

allow the Cloud Run Service Account access to the secret

Berglas Encrypt

Runtime Decryption

Berglas has a minimal Docker image containing all deps required to fetch a secret, decrypt it and write it to Cloud Run environment variables. It runs prior to our own Cloud Run code which will be detailed later.

Berglas on GCR

Berglas Request Lifecycle

Here we get a better overview of what we’re trying to achieve:

Berglas Request Lifecycle

1. requests comes into container and are consumed by Berglas

2. encrypted secrets are fetched from GCS

3. the secrets are decrypted and placed into Cloud Run Environment Variables

4. the request is forward to the Node.js server

5. decrypted secrets are read from process.env.*

6. the server sends back a response

Node.js on Cloud Run example

Now that we know how Berglas works, let’s dive into our example. The goal here is to execute the following in our GCP project’s Cloud Shell environment:

Clone the example code

Bootstrap Berglas and Encrypt a secret

Build our Node.js Cloud Run image

Deploy our image to Cloud Run

The example code in GCP Cloud Shell

Clone the example

git clone https://github.com/jthegedus/firebase-gcp-examples/

Berglas Bootstrap & Encrypt

I created some shell scripts to get going more quickly (These are rather crude and it is not advised to use these particular scripts). We just need to add exec permissions to the files.

cd firebase-gcp-examples/gcp-cloudrun-berglas/berglas-setup

chmod +x 01-bootstrap.sh 02-encrypt.sh

⚠️ Peruse the code before executing

Initialise Berglas with API access, a GCS Bucket, KMS Keyring and Audit logging.

./01-bootstrap.sh

Encrypt and store a secret with berglas create command and grant IAM access to the Cloud Run Service Account.

./02-encrypt.sh “api-key” “super secret test api key”

Docker & Node.js Code

Before we go further, let’s look at the code in more depth:

Our Docker image is Node.js 10 Alpine with ca-certificates added (a requirement of Cloud Run), our copied source code and production dependencies.

The last set of COPY commands bring in the Berglas binary via the aforementioned Docker image. Finally, the ENTRYPOINT is set to run Berglas and our package.json ’s yarn start command.

Our only dependency is the aforementioned Functions Framework which is used in the start script to run the helloBerglas function.

The JavaScript code here utilises the new Node.js Functions Framework which unpacks the HTTP event received by Cloud Run (see the Consistent Eventing section of my last post for more detail).

We expect our secret to be defined in the process.env.API_KEY environment variable (we will define this during the deploy step). We simply return it as text in the response (⚠️ do not do this if you want your secrets to be secret!).

Build our Node.js Cloud Run Image

Run gcloud build with our Dockerfile pushing the result to Google Container Registry.

cd ../helloService

chmod +x build.sh

./build.sh

Our final image in GCR is only 32.5MB, thanks alpine!

Deploy your Image to Cloud Run

Run gcloud beta run deploy berglas-example-node … to deploy our berglas-example-node GCR image to Cloud Run.

chmod +x deploy.sh

./deploy.sh “api-key”

The key part of this script is the flag

--set-env-vars "API_KEY=berglas://${BUCKET_ID}/${1}" \

which sets an environment variable in Cloud Run to be interpretted by Berglas and fetched from the correct GCS location.