Search code examples
aws-lambdaaws-codepipelineaws-serverlessdotenvssm

Passing secrets to lambda during deployment stage (CodePipeline) with Serverless?


I have a CodePipeline with GitHub as a source, set up. I am trying to, without a success, pass a single secret parameter( in this case a Stripe secret key, currently defined in an .env file -> explaination down below ) to a specific Lambda during a Deployment stage in CodePipeline's execution.

Deployment stage in my case is basically a CodeBuild project that runs the deployment.sh script:

#! /bin/bash npm install -g [email protected] serverless deploy --stage $env -v -r eu-central-1

Explanation:

I've tried doing this with serverless-dotenv-plugin, which serves the purpose when the deployment is done locally, but when it's done trough CodePipeline, it returns an error on lambda's execution, and with a reason:

Since CodePipeline's Source is set to GitHub (.env file is not commited), whenever a change is commited to a git repository, CodePipeline's execution is triggered. By the time it reaches deployment stage, all node modules are installed (serverless-dotenv-plugin along with them) and when serverless deploy --stage $env -v -r eu-central-1 command executes serverless-dotenv-plugin will search for .env file in which my secret is stored, won't find it since there's no .env file because we are out of "local" scope, and when lambda requiring this secret triggers it will throw an error looking like this:

On further inspection this error transforms into {message:"Missing authentication token"}

So my question is, is it possible to do it with dotenv/serverless-dotenv-plugin, or should that approach be discarded? Should I maybe use SSM Parameter Store or Secrets Manager? If yes, could someone explain how? :)


Solution

  • So, upon further investigation of this topic I think I have the solution. SSM Parameter Store vs Secrets Manager is an entirely different topic, but for my purpose, SSM Paremeter Store is a choice that I chose to go along with for this problem. And basically it can be done in 2 ways.

    1. Use AWS Parameter Store

    Simply by adding a secret in your AWS Parameter Store Console, then referencing the value in your serverless.yml as a Lambda environement variable. Serverless Framework is able to fetch the value from your AWS Parameter Store account on deploy.

    provider: environement: stripeSecretKey: ${ssm:stripeSecretKey}

    Finally, you can reference it in your code just as before:

    const stripe = Stripe(process.env.stripeSecretKey);

    PROS: This can be used along with a local .env file for both local and remote usage while keeping your Lambda code the same, ie. process.env.stripeSecretKey

    CONS: Since the secrets are decrypted and then set as Lambda environment variables on deploy, if you go to your Lambda console, you'll be able to see the secret values in plain text. (Which kinda indicates some security issues)

    That brings me to the second way of doing this, which I find more secure and which I ultimately choose:

    2. Store in AWS Parameter Store, and decrypt at runtime To avoid exposing the secrets in plain text in your AWS Lambda Console, you can decrypt them at runtime instead. Here is how:

    • Add the secrets in your AWS Parameter Store Console just as in the above step.

    • Change your Lambda code to call the Parameter Store directly and decrypt the value at runtime:

      import stripePackage from 'stripe'; const aws = require('aws-sdk'); const ssm = new aws.SSM();

      const stripeSecretKey = ssm.getParameter( {Name: 'stripeSecretKey', WithDecryption: true} ).promise(); const stripe = stripePackage(stripeSecretKey.Parameter.Value);

    (Small tip: If your Lambda is defined as async function, make sure to use await keyword before ssm.getParameter(...).promise(); )

    PROS: Your secrets are not exposed in plain text at any point.

    CONS: Your Lambda code does get a bit more complicated, and there is an added latency since it needs to fetch the value from the store. (But considering it's only one parameter and it's free, it's a good trade-off I guess)

    For the conclusion I just want to mention that all this in order to work will require you to tweak your lambda's policy so it can access Systems Manager and your secret that's stored in Parameter Store, but that's easily inspected trough CloudWatch.

    Hopefully this helps someone out, happy coding :)