Search code examples
typescriptaws-cdkaws-secrets-manager

AWS SecretsManager value won't resolve


I'm using aws-cdk-lib (2.13.0). Here's a snippet of my code:

import { App, Stack } from 'aws-cdk-lib';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';

export class CognitoStack extends Stack {
  constructor(scope: App) {
    super(scope, 'cognito');

    const secret = this.getSecret('google');
    console.log({ secret });
  }

  public getSecret(path: string) {
    const secret = Secret.fromSecretNameV2(this, `Secret${path}`, path);
    console.log({ path, secret, secretArn: secret.secretArn, string: secret.secretValue.toString() });
    return secret.secretValue.toJSON();
  }
}

The resulting logs look like this:

{
  path: 'google',
  secret: <ref *1> SecretBase {
    node: Node {
      host: [Circular *1],
      _locked: false,
      _children: {},
      _context: {},
      _metadata: [],
      _dependencies: Set(0) {},
      _validations: [Array],
      id: 'Secretgoogle',
      scope: [CognitoStack]
    },
    stack: CognitoStack {
      node: [Node],
      _missingContext: [],
      _stackDependencies: {},
      templateOptions: {},
      _logicalIds: [LogicalIDs],
      account: '${Token[AWS.AccountId.4]}',
      region: '${Token[AWS.Region.8]}',
      environment: 'aws://unknown-account/unknown-region',
      terminationProtection: undefined,
      _stackName: 'cognito',
      tags: [TagManager],
      artifactId: 'cognito',
      templateFile: 'cognito.template.json',
      _versionReportingEnabled: true,
      synthesizer: [DefaultStackSynthesizer],
      [Symbol(@aws-cdk/core.DependableTrait)]: [Object]
    },
    env: {
      account: '${Token[AWS.AccountId.4]}',
      region: '${Token[AWS.Region.8]}'
    },
    _physicalName: undefined,
    _allowCrossEnvironment: false,
    physicalName: '${Token[TOKEN.332]}',
    encryptionKey: undefined,
    secretName: 'google',
    secretArn: 'arn:${Token[AWS.Partition.7]}:secretsmanager:${Token[AWS.Region.8]}:${Token[AWS.AccountId.4]}:secret:google',
    autoCreatePolicy: false,
    [Symbol(@aws-cdk/core.DependableTrait)]: { dependencyRoots: [Array] }
  },
  secretArn: 'arn:${Token[AWS.Partition.7]}:secretsmanager:${Token[AWS.Region.8]}:${Token[AWS.AccountId.4]}:secret:google',
  string: '${Token[TOKEN.333]}'
}
{ secret: '<unresolved-token>' }

The results of the npx cdk diff sandbox-cognito look like this:

Stack sandbox-cognito
Resources
[~] AWS::Cognito::UserPoolIdentityProvider Google GoogleAF1E99FA
 └─ [~] ProviderDetails
     ├─ [-] Removed: .client_id
     └─ [-] Removed: .client_secret

Which means that it is removing the client_id/client_secret that I was able to set manually. Now that I'm trying to load the values from a secret, it is not working.

The problem is that I cannot parse the JSON (notice the <unresolved-token> in the logs. I think that it is not yet resolved, but I'm not sure how to resolve... It's trying parse this string literal: ${Token[TOKEN.333]}, instead of the secret value. How can I get the results of the secret string?


Solution

  • Import your existing secret as a SecretValue. Pass it to the clientSecret:string prop using the .toString() method.

    // Existing secret as SecretValue.  Or use Secret.fromSecretNameV2.
    const secretVal = cdk.SecretValue.secretsManager('GoogleSecrets', {
      jsonField: 'client-secret',
    });
    
    new cognito.UserPoolIdentityProviderGoogle(this, 'GoogleProvider', {
      userPool,
      // creates a dynamic reference which resolves to the actual secret value at deploy-time
      clientSecret: secretVal.toString(),
      clientId: 'my-id',
    });
    

    Explanation

    SecretValue.toString() "resolves" to different values during the lifecycle: When you console.log it, you get an (useless) opaque placeholder Token value like ${Token[TOKEN.198]}. At synth-time CDK renders a CloudFormation dynamic reference in the template:

    //my-stack.template.json
    {"client_secret": "{{resolve:secretsmanager:arn:aws:secretsmanager:us-east-1:123456789012:secret:GoogleSecrets:SecretString:client-secret::}}"}
    

    At deploy-time, CloudFormation "resolves" the actual secret value from the dynamic reference.

    The important takeaway is that the actual secret value is never exposed to your local environment or the template artefacts.