Search code examples
typescriptaws-cdkalexa-skills-kit

Why will my lambda skill cdk construct not accept the cdk.secretvalue parameters i am passing in?


Using aws-cdk": "2.47.0" I am getting the following error when deploying my stack

Resolution error: Synthing a secret value to . 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..

Object creation stack:
  at new Intrinsic (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/aws-cdk-lib/core/lib/private/intrinsic.js:1:680)
  at new SecretValue (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/aws-cdk-lib/core/lib/secret-value.js:1:592)
  at Function.cfnDynamicReference (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/aws-cdk-lib/core/lib/secret-value.js:1:2713)
  at Function.secretsManager (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/aws-cdk-lib/core/lib/secret-value.js:1:2202)
  at HltbStack.retrieveSecrets (/Users/john/Desktop/Production/alexa skills/hltb/lib/hltb-stack.ts:55:45)
  at new HltbStack (/Users/john/Desktop/Production/alexa skills/hltb/lib/hltb-stack.ts:27:12)
  at Object.<anonymous> (/Users/john/Desktop/Production/alexa skills/hltb/bin/hltb.ts:20:1)
  at Module._compile (internal/modules/cjs/loader.js:1085:14)
  at Module.m._compile (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/ts-node/src/index.ts:1618:23)
  at Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
  at Object.require.extensions.<computed> [as .ts] (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/ts-node/src/index.ts:1621:12)
  at Module.load (internal/modules/cjs/loader.js:950:32)
  at Function.Module._load (internal/modules/cjs/loader.js:790:12)
  at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
  at phase4 (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/ts-node/src/bin.ts:649:14)
  at bootstrap (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/ts-node/src/bin.ts:95:10)
  at main (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/ts-node/src/bin.ts:55:10)
  at Object.<anonymous> (/Users/john/Desktop/Production/alexa skills/hltb/node_modules/ts-node/src/bin.ts:800:3)
  at Module._compile (internal/modules/cjs/loader.js:1085:14)
  at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
  at Module.load (internal/modules/cjs/loader.js:950:32)
  at Function.Module._load (internal/modules/cjs/loader.js:790:12)
  at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
  at /Users/john/.nvm/versions/node/v14.20.1/lib/node_modules/npm/node_modules/libnpx/index.js:268:14

This is what my stack looks like

import { Stack } from "aws-cdk-lib";
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { Skill } from "cdk-alexa-skill";
import * as path from "path";
import { Props } from "../bin/hltb";
import * as ssm from "aws-cdk-lib/aws-ssm";

export type SkillConfig = {
  alexaVendorIdSecretValue: string;
  lwaClientIdSecretValue: string;
  lwaClientSecretSecretValue: string;
  lwaRefreshTokenSecretValue: string;
};

export class HltbStack extends Stack {
  constructor(scope: cdk.App, id: string, props: Props) {
    super(scope, id, props);

    const skillBackendLambdaFunction = new lambda.Function(this, "Function", {
      runtime: lambda.Runtime.NODEJS_16_X,
      handler: "handler.handler",
      code: lambda.Code.fromAsset(path.join(__dirname, "/../src/handlers")),
    });

    const { alexaVendorId, lwaClientId, lwaClientSecret, lwaRefreshToken } =
      this.retrieveSecrets(props);

    const skill = new Skill(this, "hltbSkill", {
      endpointLambdaFunction: skillBackendLambdaFunction,
      skillPackagePath: "src/skill-package",
      alexaVendorId,
      lwaClientId,
      lwaClientSecret,
      lwaRefreshToken,
    });
  }

  private retrieveSecrets(props: Props): {
    alexaVendorId: string;
    lwaClientId: string;
    lwaClientSecret: cdk.SecretValue;
    lwaRefreshToken: cdk.SecretValue;
  } {
    const alexaVendorId = ssm.StringParameter.valueForStringParameter(
      this,
      props.alexaVendorIdSecretValue
    );

    const lwaClientId = ssm.StringParameter.valueForStringParameter(
      this,
      props.lwaClientIdSecretValue
    );

    const lwaClientSecret = cdk.SecretValue.secretsManager(
      props.lwaClientSecretSecretValue
    );
    const lwaRefreshToken = cdk.SecretValue.secretsManager(
      props.lwaRefreshTokenSecretValue
    );

    return {
      alexaVendorId,
      lwaClientId,
      lwaClientSecret,
      lwaRefreshToken,
    };
  }
}

here is the type for the skill

export interface SkillProps {
    readonly endpointLambdaFunction?: lambda.IFunction;
    readonly skillPackagePath: string;
    readonly alexaVendorId: string;
    readonly lwaClientId: string;
    readonly lwaClientSecret: cdk.SecretValue;
    readonly lwaRefreshToken: cdk.SecretValue;
}

I am passing it a cdk.SecretValue for the lwaClientSecret and client lwaRefreshToken so why am i getting the error message?


Solution

  • Quick and dirty: "@aws-cdk/core:checkSecretUsage": false


    The CDK tries to protect you against leaking secrets into the CDK artefacts. The error message is generated by one of these checks. In your case, though, it seems the error message is a false positive. From what I can see, your code does not expose plaintext secrets*.

    You can force the CDK to shut up by disabling the @aws-cdk/core:checkSecretUsage feature flag (default false, recommended true):

    Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations (config)

    Ideally you'd address the root cause (perhaps the 3rd Party Skill construct?) so the check can remain enabled. But disabling the flag should silence the error.


    * Your retrieveSecrets method does not actually "retrieve" the secrets. The CDK is not querying SecretsManager for the actual secrets. That would be unsafe. Rather, the cdk.SecretValue.secretsManager method generates a CloudFormation dynamic reference to the secret. In the template, you'll get something like {{resolve:secretsmanager:lwaClientSecretSecretValue:SecretString:::}}. Perfectly safe. The actual secret value is only resolved cloud-side by CloudFormation when you deploy the app. You can review the template artefact in cdk.out and verify this for yourself.