< Back to articles

Cloud Run vs Secret Management for NodeJs Apps

As every DevOps team these days, we too struggle with application delivery. To be more precise: we want to make the process of code delivery and infrastructure management as painless as possible. Yes, there are obviously corners which couldn’t be cut and the issues to make a man with. But overall, that’s why we are here: To provide reasonable app delivery to the platform best suiting the app demands. Having a backend API that is not used all the time, we have chosen Cloud Run as the platform. But what about the secret management?

Well, if you were wondering if there is any support for secrets, wonder no more. It took a while since GCP Secret Manager was introduced (10.12.2019) and preview support to Cloud Run was implemented (12.5.2021). Some people would say that using secrets in KNative (which is the underlying system for Cloud Run) is not that hard to implement, but I am sure that’s just a blasphemy and Google has its reasons. In this small blog post, we are going to see how to use Secret Manager in Cloud Run. For that we’ll use Configuru, a wonderful small package helping us manage dotenv files in Ackee projects.

First, we have to start with setting up the environment variable CFG_JSON_PATH in Dockerfile

\# BUILDER IMAGE  
FROM node:14.16.0\-buster AS builder  
...WORKDIR /usr/src/app  
ENV CFG\_JSON\_PATH="/config/secrets.json"  
...

The variable can be also set in Cloud Run itself. The main point here is to tell Configuru where to find the JSON file containing the secrets. Configuru uses this variable and reads the file it points to.

We picked /config as the mount point due to issues with permissions. Once we mounted secrets into the app directory (/usr/src/app), Cloud Run reported following issue:

Could not open file at path /usr/src/app/node_modules. The path is in a mounted secrets volume, but the exact path does not correspond to any secret specified in the mount configuration.

In a way, this actually makes a lot of the folders from FHS unusable. Once you are not sure, where your apps writes down or reads from, choose rather a new folder. That’s why we used /config.

Now we can use Secret Manager. Create a new application secret with Terraform:

resource "google\_secret\_manager\_secret" "secret" {  
  secret\_id = "secret"  ...  
}

Do not forget to use JSON for the input value. So far, Secret Manager does not provide any input validation and could be used for any type of data. But Configuru only supports JSON and JSONC. If JSON is not formatted correctly, the debug output can be hard to interpret. Let’s use this simple secret:

{  
  "A\_SECRET": "hello world from GCP Secret Manager"  
}

To give access to the secrets, give the Service Account used for Cloud Run service role name /Secret Manager Secret Accessor. To honour the Principle of least privilege, you can assign permissions only for one particular secret. Let’s use an example from Terraform documentation:

resource "google\_secret\_manager\_secret\_iam\_member" "member" {  
  project = "PROJECT-ID"  
  secret\_id = google\_secret\_manager\_secret.secret.secret\_id  
  role = "roles/secretmanager.secretAccessor"  
  member = "serviceAccount:sa@PROJECT-ID.iam.gserviceaccount.com"  
}

Where resource google_secret_manager_secret.secret is a secret we created earlier with terraform.

Now we can configure Cloud Run itself. Due to label PREVIEW which is currently present in the configuration UI of the Cloud Run secrets, I thought you would rather benefit from YAML manifest representation:

...  
  
spec:  
  
  ...  
  template:  
    spec:  
  
      ...  
      serviceAccountName: serviceAccount:sa@PROJECT-ID.iam.gserviceaccount.com  
  containers:  
  
      - image: ...  
        volumeMounts:  
        - name: SECRET\_NAME-peg-dax-xin  
          readOnly: true  
          mountPath: /config  
      volumes:  
      - name: SECRET\_NAME-peg-dax-xin  
        secret:  
          secretName: SECRET\_NAME  
          items:  
          - key: latest  
            path: secrets.json

As you noticed, I left out big parts of the manifest which do not contribute to the Secret Manager setup. Also, I presumed you would be interested only in the latest release of the secret. In case that’s not true, adjust the key in the secret items to your desired version.

From this point onward, everything is taken care of by Configuru. To make the example complete. Let’s create a configuration module in config.ts. I will use well written example from the Getting Started part of README.md:

import { createLoader, values, safeValues } from 'configuru'  
  
const loader = createLoader()  
const secretScheme = {  
  aSecret: loader.string.hidden('A\_SECRET'),  
}  
  
export default values(secretScheme)  
export const safeConfig = safeValues(secretScheme)

Where A_SECRET is a value you defined in the Secret Manager. Now you can use the variable anywhere in your code:

import config, { safeConfig } from './config'  
  
   
  
console.log(config.aSecret) // hello \*\*\*nager  
console.log(safeConfig.aSecret) // hello world from GCP Secret Manager

And we are done. In case you would rather appreciate using environment variables instead of mounting a JSON file, keep in mind that Configuru has storage precedence. For our use-case, the JSON file seems much more simple because it needs only one environment variable CFG_JSON_PATH and that’s all. Hopefully, you found here something you can use in your next project. Also, I hope I didn’t mess up something and the example works as it should. If not, let me know in the comments

Martin Beránek
Martin Beránek
DevOps Team Lead

Are you interested in working together? Let’s discuss it in person!