Search code examples
javascriptnode.jsasync-awaitsecretsmanagergcp-secrets-manager

How to set process.env from a function?


I'm very open to learning if there's a better "best practices" way to do this, but I have some scripts that I run occasionally that edit a database, and so I need to pass the DB password for those scripts. I'm getting the password by calling a function that calls google cloud Secrets Manager, and I'm unable to add it to the process.env.

for example if I put this at the top of my script file:

process.env.DB_HOST='127.0.0.1';
process.env.DB_USER='michael';
process.env.DB_NAME='staging-db';
process.env.DB_PORT=1234;
process.env.DB_PASS= await accessSecret('projects/myproject-123/secrets/DB_PASS/versions/latest');

When the above runs I get the error
SyntaxError: await is only valid in async functions and the top level bodies of modules

But, if I move the process.env.DB_PASS setting inside my async main() function, then it has local scope to that main function. Other files called by functions in this script see process.env.DB_PASS as undefined (but do see values for any process.env variables set globally at the top of the file.

How do I pull in and set that secret without actually pasting the literal secret into the code?

To represent the problem of the scoping, here's a working-code recreation of that problem in action. This is the script file I'm running:

process.env.DB_HOST='127.0.0.1';
process.env.DB_USER='michael';
process.env.DB_NAME='staging-db';
process.env.DB_PORT=1234;
const db = require('../../src/database/process_pull_test');


const main = async () => {
  process.env.SCOPED_KEY = "helloimscoped"
  db.hello();
}

main().catch((e) => {console.error(e)});

Here is the process_pull_test file

console.log("SCOPED KEY", process.env.SCOPED_KEY);
const dbHost = process.env.DB_HOST;
const dbUser = process.env.DB_USER;
const dbName = process.env.DB_NAME;
const dbPort = process.env.DB_PORT;
const scopedKey = process.env.SCOPED_KEY;

async function hello() {
  console.log(dbHost);
  console.log(dbUser);
  console.log(dbName);
  console.log(dbPort);
  console.log(scopedKey);
  return console.log("Hello Secrets");
}


module.exports = {
  hello: hello
}

And, here is the output

SCOPED KEY undefined
127.0.0.1
michael
staging-db
1234
undefined
Hello Secrets

Solution

  • Typically you use some sort of configuration tool such as dotenv or config to manage app settings and secrets. Be sure NOT to save the secret files in source control.

    To get your db secret, you have to make an async call to Google and get the response before you attempt to read any variables that need that data.

    I'll assume you're just running this from the command line.

    First you'll need an async iife to execute your main:

    (async () => {
        // you could put your main code here.
        await main();
    })();
    

    Next, add a require to your Google secrets manager. It can go anywhere, but you should make sure you have access to it where you only call it once. You might as well do it in your main file.

    const gcsm = require('./secretManager');
    

    Then call your Google secrets manager in main. (I'm making up the api.) and set your env. You will also need to await your call to db.hello() when you actually put async code in it. Technically, you don't need to do it with the code as written.

    const main = async () => {
      const dbPassword = await gcsm.getSecret('some config location');
      process.env.DB_PASS = dbPassword;
      process.env.SCOPED_KEY = "helloimscoped"
      await db.hello();
    }
    

    In hello() in process_pull_test, read the env variable.

    async function hello() {
      console.log(process.env.DB_PASS);
      console.log(dbHost);
      console.log(dbUser);
      console.log(dbName);
      console.log(dbPort);
      console.log(scopedKey);
      // note the following line doesn't return anything because
      // console.log returns undefined. You probably want to return a 
      // string value to test for now.
      return console.log("Hello Secrets");
    }   
    

    This should get you going.

    Additional comments:

    • When you first log SCOPED_KEY in process_pull_test it isn't scoped it's just logged at require time and is still undefined.
    • When you log scopedKey in hello(), it's still undefined because you set it at require time at the top of process_pull_test.
    • When you set SCOPED_KEY in main(), it's too late, you've already saved it to scopedKey as undefined at require time. If you were to set scopedKey to process.env.SCOPED_KEY in hello() before you log it, you would see your updated value.