Let's use AWS Secrets Manager with AWS Lambda to keep the secrets safe.
To manage infrastructure, I use Terraform, but you can do the same through AWS CLI or web interface. One caveat is that I don't assign the secrets in Terraform because we don't want secrets in Terraform's state files. Instead, I do that through the AWS web interface. Here I create variables. Then I make a policy to allow Lambda read those variables and finally attach that policy to Lambda's IAM.
resource "aws_secretsmanager_secret" "auth_secret" {
name = "twitter_client_secret"
}
resource "aws_secretsmanager_secret" "google_client_secret" {
name = "storybot_mnemonic_key_staging"
}
resource "aws_iam_policy" "secrets" {
name = "tf-${var.name}-secrets"
path = "/"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "secretsmanager:GetSecretValue",
"Resource": "${aws_secretsmanager_secret.auth_secret.arn}",
"Effect": "Allow"
},
{
"Action": "secretsmanager:GetSecretValue",
"Resource": "${aws_secretsmanager_secret.google_client_secret.arn}",
"Effect": "Allow"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "secrets" {
role = module.api.lambda_iam_role_name
policy_arn = aws_iam_policy.secrets.arn
}
To make the Lambda know the names of those secrets, we can pass them as environment variables to the lambda.
environment {
variables = {
GOOGLE_CLIENT_SECRET_ASM_NAME = aws_secretsmanager_secret.google_client_secret.name
AUTH_SECRET_ASM_NAME = aws_secretsmanager_secret.auth_secret.name
}
}
To assess those env variables with the names of secrets, I use the assertEnvVar function that will throw an error if the variable is missing.
type VariableName = "GOOGLE_CLIENT_SECRET_ASM_NAME" | "AUTH_SECRET_ASM_NAME"
export const assertEnvVar = (name: VariableName): string => {
const value = process.env[name]
if (!value) {
throw new Error(`Missing ${name} environment variable`)
}
return value
}
To get the secrets themselves, I have the assertSecret function that receives an argument of SecretName type, takes the variable name through the assertEnvVar function, and uses an instance of SecretsManager to get the value.
import { SecretsManager } from "aws-sdk"
import { memoize } from "lodash"
import { assertEnvVar } from "./assertEnvVar"
type SecretName = "GOOGLE_CLIENT_SECRET" | "AUTH_SECRET"
const secretsManagerClient = new SecretsManager()
export const assertSecret = memoize(
async (name: SecretName): Promise<string> => {
const secretAsmName = assertEnvVar(`${name}_ASM_NAME`)
const { SecretString: value } = await secretsManagerClient
.getSecretValue({ SecretId: secretAsmName })
.promise()
if (!value) {
throw new Error(`Missing ${secretAsmName} secret`)
}
return value
}
)