Aws-cdk: Cognito circular reference when setting lambda trigger permissions

Created on 25 Mar 2020  路  10Comments  路  Source: aws/aws-cdk

Create a lambda
Create a user pool
Assign the lambda to one of the user pool triggers
Set the permissions on the lambda to call Cognito APIs against the user pool
Get circular reference error in cdk deploy

Reproduction Steps

    const postAuthentication = new lambda.Function(this, "postAuthentication", {
      description: "Cognito Post Authentication Function",
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: "postAuthentication.handler",
      code: lambda.Code.asset("dist/postAuthentication"),
      timeout: cdk.Duration.seconds(30),
      memorySize: 256,
      environment: {},
    });

    const userPool = new cognito.UserPool(this, userPoolName, {
     ....
      lambdaTriggers: {
        postAuthentication,
      },
    });

    const postAuthPermissionPolicy = new iam.PolicyStatement({
      actions: ["cognito-idp:AdminDeleteUserAttributes", "cognito-idp:AdminAddUserToGroup"],
      resources: [userPool.userPoolArn],
    });
   // now give the postAuthentication lambda permission to change things
    postAuthentication.addToRolePolicy(postAuthPermissionPolicy);

Error Log

Cognito failed: Error [ValidationError]: Circular dependency between resources

Environment

  • CLI Version : 1.31.0
  • Framework Version:
  • OS :
  • Language : Typescript

Other


This is :bug: Bug Report

@aws-cdaws-lambda bug efforlarge no-autoclose p2

Most helpful comment

Leave this open. I'd like to get a better solution for this in place. It's more complicated than I originally thought it would be.

All 10 comments

Your user pool requires your Lambda function to be created first, but your function's policy depends on on your user pool's ARN, causing the circular dependency. Removing resources: [userPool.userPoolArn] should fix this, although you would need to make sure that doesn't pose a security risk.

Providing the functionName property to the lambda.Function should resolve this issue. Can you give that a shot?

Sorry, that didn't work. I added functionName to the lambda:

    const postAuthentication = new lambda.Function(this, "postAuthentication", {
      description: "Cognito Post Authentication Function",
      functionName: stackName + "-postAuthentication",
      runtime: lambda.Runtime.NODEJS_12_X,
      handler: "postAuthentication.handler",
      code: lambda.Code.asset("dist/postAuthentication"),
      timeout: cdk.Duration.seconds(30),
      memorySize: 256,
      environment: {},
    });

Then after the user pool is defined I added:

    const postAuthPermissionPolicy = new iam.PolicyStatement({
      actions: ["cognito-idp:AdminDeleteUserAttributes", "cognito-idp:AdminAddUserToGroup"],
    });
    postAuthPermissionPolicy.addResources(userPool.userPoolArn);
    postAuthenticationLambda.addToRolePolicy(postAuthPermissionPolicy);

As soon as I switched out postAuthPermissionPolicy.addAllResources to postAuthPermissionPolicy.addResources(userPool.userPoolArn) the circular reference error came back.

In other CDK areas there are grant* functions that seem to do this magic. Any other ideas of a work around until we get #7112 ?

The grant* functions do not solve this problem. However, I've classified this as a bug.

@markcarroll -

The workaround for this issue is to not use the addToRolePolicy() but instead to attachInlinePolicy(). See code snippet below -

import { UserPool } from '@aws-cdk/aws-cognito';
import { Function, Code, Runtime } from '@aws-cdk/aws-lambda';
import { Policy, PolicyStatement } from '@aws-cdk/aws-iam';
import { App, Stack } from '@aws-cdk/core';

const app = new App();
const stack = new Stack(app, 'mystack');

const fn = new Function(stack, 'fn', {
  code: Code.fromInline('foo'),
  runtime: Runtime.NODEJS_12_X,
  handler: 'index.handler',
});

const userpool = new UserPool(stack, 'pool', {
  lambdaTriggers: {
    userMigration: fn
  }
});

fn.role!.attachInlinePolicy(new Policy(stack, 'userpool-policy', {
  statements: [ new PolicyStatement({
    actions: ['cognito-idp:DescribeUserPool'],
    resources: [userpool.userPoolArn],
  }) ]
}));

Can you check if this fixes this issue for you?

This issue has not received a response in a while. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.

Please ignore the message from the bot. I've removed the labels that triggered this.

The attachInlinePolicy workaround seems to do the trick. Do you want to close this or keep it open?

Leave this open. I'd like to get a better solution for this in place. It's more complicated than I originally thought it would be.

discussed this issue with @nija-at and @rix0rrr

deployed this sample application inspired by the OP

import { PolicyStatement } from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import { App, Stack } from '@aws-cdk/core';
import { UserPool } from '../lib';

const app = new App();
const stack = new Stack(app, 'integ-user-pool-client-explicit-props');

const fn = new lambda.Function(stack, 'fn', {
  code: lambda.Code.fromInline('foo'),
  runtime: lambda.Runtime.NODEJS_12_X,
  handler: 'index.handler',
});

const userpool = new UserPool(stack, 'pool', {
  lambdaTriggers: {
    postAuthentication: fn,
  },
});

const postAuthPermissionPolicy = new PolicyStatement({
  actions: ['cognito:DescribeUserPool'],
  resources: [userpool.userPoolArn],
});
// now give the postAuthentication lambda permission to change things
fn.addToRolePolicy(postAuthPermissionPolicy);

Screen Shot 2020-10-19 at 11 38 33 PM

this produced a stack with a circular dependency -> the trigger Lambda function has a dependency on the role policy, which has a dependency on the user pool, which has a dependency on the lambda function.

The problematic edge is the one between the function and the role policy - this should not be necessary as the role policy is already depending on the Lambda execution role (which the Lambda function also depends on). This problem will appear in any resource that has a Lambda function and a subsequent call to addToRolePolicy. It may not be possible to reliably remove that depends on with the current implementation in the lambda module.

As mentioned earlier by @nija-at, the workaround is to use attachInlinePolicy which does not create a dependency between the Lambda function and the inline policy.

import { Policy, PolicyStatement } from '@aws-cdk/aws-iam';
import * as lambda from '@aws-cdk/aws-lambda';
import { App, Stack } from '@aws-cdk/core';
import { UserPool } from '../lib';

const app = new App();
const stack = new Stack(app, 'integ-user-pool-client-explicit-props');

const fn = new lambda.Function(stack, 'fn', {
  code: lambda.Code.fromInline('foo'),
  runtime: lambda.Runtime.NODEJS_12_X,
  handler: 'index.handler',
});

const userpool = new UserPool(stack, 'pool', {
  lambdaTriggers: {
    postAuthentication: fn,
  },
});

fn.role?.attachInlinePolicy(new Policy(stack, 'userpool-policy', {
  statements: [new PolicyStatement({
    actions: ['cognito-idp:DescribeUserPool'],
    resources: [userpool.userPoolArn],
  })],
}));

Screen Shot 2020-10-19 at 11 42 11 PM

This issue is now being tracked under the lambda module

Was this page helpful?
0 / 5 - 0 ratings