Matthias S
Matthias S

Reputation: 3553

Pass AWS SM Secret Key to Lambda Environment with CDK

I am having some trouble getting a specific Secrets Manager Secret key value to pass it to my lambda through CDK.

After some time I finally realized that my SecretValue is only resolved when I actually deploy this to lambda, and not while running local through SAM CLI. By doing

cdk.SecretValue.secretsManager(secretId).toString()

I get something like "{\"apiKey\":\"sdfsdf-sdfsdf-sddsf\"}", but I want to rather have the apiKey directly. Unfortunately, in my CDK code, I cannot JSON:parse(...secretsManager(..).toString()) as this will only be resolved once deployed. Before, the value is simply: {{resolve:secretsmanager:apiKey:SecretString:::}} (which seems to be a Token: https://docs.aws.amazon.com/cdk/latest/guide/tokens.html)

So I guess I would need some way to tell CDK how to use the rendered value, maybe by passing a callback that transforms the rendered result - is that possible? Are there any other tools I can use in my CDK setup that allow me to receive a specific key from a secret so that I can pass it to lambda directly?

I hope the problem is understandable. Thanks in advance for your help.

Upvotes: 5

Views: 14608

Answers (4)

Jack Rogers
Jack Rogers

Reputation: 695

python version

environment_variables = secretsmanager.Secret.from_secret_complete_arn(
        self,
        "environment_variables",
        "arn:aws:secretsmanager:us-east-1:1234:secret:my-keys",
    )

lambda = _lambda.Function(
        self, "MyLambda",
        function_name="my-function",
        runtime=_lambda.Runtime.PYTHON_3_9,
        handler="lambda_function.lambda_handler",
        timeout=cdk.Duration.seconds(3),
        architecture=_lambda.Architecture.ARM_64,
        code=_lambda.Code.from_asset(os.path.join(dirname, '../app_lambdas/my_lambda')),
        role=my_lambda_role,
        environment={
            "API_KEY": environment_variables.secret_value_from_json('api-key').unsafe_unwrap()
        },
        )

Upvotes: 1

Jason Wadsworth
Jason Wadsworth

Reputation: 8887

You need to use Secret. You can use any of the static from methods to get the secret. From there you can use the secretValueFromJson method to get the value.

Example (secret for Postgres db):

import * as secretsmanager from '@aws-cdk/aws-secretsmanager';

const dbSecret = secretsmanager.Secret.fromSecretNameV2(this, 'db-secret', 'db-secret-name');
const dbUser = dbSecret.secretValueFromJson('username').toString();
const dbPass = dbSecret.secretValueFromJson('password').toString();
const dbName = dbSecret.secretValueFromJson('dbname').toString();

Upvotes: 9

bln_dev
bln_dev

Reputation: 2411

in 2022

Using .toString() on Secrets seems no longer accepted without calling .unsafeUnwrap() first. As far as the documentation goes:

If you don't call this method, using the secret value directly in a string context or as a property value somewhere will produce an error.

For me, it produced the error on cdk deploy:

Resolution error: Synthing a secret value to Resources(...) Using a SecretValue here risks exposing your secret. Only pass SecretValues to constructs that accept a SecretValue property, or call AWS Secrets Manager directly in your runtime code. Call 'secretValue.unsafeUnwrap()' if you understand and accept the risks..

Solution

  1. as the message suggests try to use frameworks, which accept a SecretValue, or if you don't have the chance (as in my case I am trying to use Prisma, and it doesn't accept it yet)

  2. use .unsafeUnwrap(). Like:

const getValueFromSecret = (secret: ISecret, key: string): string => {
  return secret.secretValueFromJson(key).unsafeUnwrap()
}

// usage (e.g. in Lambda stack)
const password = getValueFromSecret(secret, 'password')

Upvotes: 15

milan
milan

Reputation: 12402

I solved this for Parameter Store with AWS SDK, here's an extract from my stack.ts:

import { SSM } from 'aws-sdk'

const ssmSDK = new SSM()

async function fetchParam(name: string): Promise<string> {
  try {
    const param = await ssmSDK
      .getParameter({
        Name: name,
        WithDecryption: true,
      })
      .promise()
    if (!param.Parameter || !param.Parameter.Value) {
      throw new Error(`${name} parameter not found!`)
    }
    return param.Parameter.Value
  } catch (err) {
    throw new Error(`failed to fetch ${name} parameter!`)
  }
}

export async function buildSearchServiceStack(
  scope: cdk.App,
  id: string,
  props?: cdk.StackProps
): Promise<cdk.Stack> {
  const stack = new cdk.Stack(scope, id, props)

  const [jwtSecret, elasticPassword] = await Promise.all([
    fetchParam(`/shared/jwt/SECRET`),
    fetchParam('/shared/elasticsearch/URL')])
// ..

Upvotes: -1

Related Questions