aws-get-secret

Coming from a Google (GCP) ecosystem, there are many things that AWS does not do, that you’ll miss. One of these things is automatically mounting secrets from Secret Manager on your serverless Cloud Run instances like so:

gcloud run deploy SERVICE --update-secrets=PATH=SECRET_NAME:VERSION

This is extremely convenient, as the application itself does not need to be aware of the complexities of retrieving the secret. This is a form of separation of concerns, and has the advantage that locally you can just use .env.local while remote it automatically uses Secret Manager. Combined with connection strings to in-memory databases this becomes an ideal development experience, that is easily debuggable and easy to understand.

Unfortunately, this secret integration is not offered by AWS Lambda, although it would not be a big of a leap for AWS to add this feature somewhere soon. Instead, the typical way to retrieve to retrieve a secret with AWS Lambda today would be to retrieve it at runtime:

async function getSecret(secretName, region){
  const config = { region : region }
  var secret, decodedBinarySecret;
  let secretsManager = new AWS.SecretsManager(config);
  let secretValue = await secretsManager.getSecretValue({SecretId: secretName}).promise();
  if ('SecretString' in secretValue) {
    return secretValue.SecretString;
  } else {
    let buff = new Buffer(secretValue.SecretBinary, 'base64');
    return buff.toString('ascii');
  }
}

Before Google added Secret Manager support to Cloud Run, there was another utility that I often found myself using: gcp-get-secret. This could be used like the snippet below, and the tool would invoke a next command with an environment variable containing the replaced secret value.

export MYSQL_PASSWORD=gcp:///mysql_root_password'
gcp-get-secret bash -c 'echo $MYSQL_PASSWORD'

Such a tool did not exist for AWS yet as far as I know, until today, as I just published aws-get-secret. This tool works much the same like gcp-get-secret but instead uses the prefix aws:/// and understands ARN (or the shorter secret name).

For example:

export MYSQL_PASSWORD=aws:///mysql_root_password'
aws-get-secret bash -c 'echo $MYSQL_PASSWORD'

AWS Lambda layer

While this is convenient if you control your full image (like when running ECR images on Lambdas), it can also be used with other runtimes, like NodeJS or Python, by using a Lambda Layer and the AWS_LAMBDA_EXEC_WRAPPER environment variable. If your Lambda sets this value to AWS_LAMBDA_EXEC_WRAPPER=/opt/aws-get-secret then this wraps the actual execution of the Lambda with aws-get-secret and the real handler has the real secret values available in its environment.

The layer is also published as NodeJS package, allowing you to integrate it with CDK and/or serverless-stack with just a few lines of infrastructure code:

import { wrapLambdasWithSecrets } from "aws-get-secret-lambda"

export class SomeStack extends Stack {
  constructor(scope: Construct) {
    super(scope, 'SomeStack');
    this.setDefaultFunctionProps({
        timeout: 20,
        memorySize: 512,
        environment: { MYSQL_PASSWORD: "aws:///mysql_root_password" },
    });
    // Create the HTTP API
    const api = new sst.Api(this, "Api", {
      routes: {
        "GET /notes": "src/list.main",
        "GET /notes/{id}": "src/get.main",
        "PUT /notes/{id}": "src/update.main",
      },
    });
    // Load any configured secrets in all Lambdas
    wrapLambdasWithSecrets(this.getAllFunctions());
  }
}

Try it today, and let me know if it helped you or if you have any feedback! Reach out to me at Twitter.