aws-cdk: cognito-identity-pool: Can't attach IdentityPoolRoleAttachment even if not present

Describe the bug

If you create an identity pool like this, without specifying an authenticated and unauthenticated role, it generates default roles rather than just leaving them blank.

const idPool = new IdentityPool(stack, "xxx-cognito-id-pool", {
    identityPoolName: "xxx-id-pool",
    allowUnauthenticatedIdentities: true,
    authenticationProviders: {
        userPools: [new UserPoolAuthenticationProvider({userPool: userPool})]
    }
})

Then, later, when you try to attach your own roles, like this:

const roleAttachment = new IdentityPoolRoleAttachment(stack,
    "xxx-id-pool-attachment",
    {
        identityPool: idPool,
        authenticatedRole: authenticatedRole,
        unauthenticatedRole: unauthenticatedRole
    })

it fails with this error:

CREATE_FAILED
AWS::Cognito::IdentityPoolRoleAttachment
xxx-stack/xxx-id-pool-attachment (xxxidpoolattachmentE8645D23) us-east-1:bc70b990-d038-43b8-8501-e7cd3dda6ae7 already exists in stack

Its beef seems to be that even though I created the identity pool without roles attached, it created default ones on its own. Then later when I go to ‘attach’ my own, it won’t let me because it thinks there is already an attachment.

Expected Behavior

I expected that when I create an identity pool with no roles, it would not generate default roles.

Current Behavior

Testing this with only this code and a brand new stack:

    const idPool = new IdentityPool(stack, "my-cognito-id-pool", {
        identityPoolName: "my-id-pool",
        allowUnauthenticatedIdentities: true,
        authenticationProviders: {
            userPools: [new UserPoolAuthenticationProvider({userPool: userPool})]
        }
    })

it created default IAM roles for authenticated and unauthenticated users. This caused a conflict when I then tried to create my own IdentityPoolRoleAttachment, because it says there was already an attachment.

Reproduction Steps


const idPool = new IdentityPool(stack, "my-cognito-id-pool", {
    identityPoolName: "my-id-pool",
    allowUnauthenticatedIdentities: true,
    authenticationProviders: { userPools: [new UserPoolAuthenticationProvider({userPool: userPool})] }
})


const authenticatedRole = new Role(stack, "my-cognito-authenticated-role", {
    roleName: "my-cognito-authenticated-role",
    assumedBy: new WebIdentityPrincipal('cognito-identity.amazonaws.com',
        {
            StringEquals: {
                'cognito-identity.amazonaws.com:aud': `${stack.region}:${idPool.identityPoolId}`,
            },
            'ForAnyValue:StringLike': {
                'cognito-identity.amazonaws.com:amr': 'unauthenticated',
            },
        })
});

const unauthenticatedRole = new Role(stack, "my-cognito-unauthenticated-role", {
    roleName: "my-cognito-unauthenticated-role",
    assumedBy: new WebIdentityPrincipal('cognito-identity.amazonaws.com',
        {
            StringEquals: {
                'cognito-identity.amazonaws.com:aud': `${stack.region}:${idPool.identityPoolId}`,
            },
            'ForAnyValue:StringLike': {
                'cognito-identity.amazonaws.com:amr': 'authenticated',
            },
        })
});

const roleAttachment = new IdentityPoolRoleAttachment(stack,
    "my-id-pool-attachment",
    {
        identityPool: idPool,
        authenticatedRole: authenticatedRole,
        unauthenticatedRole: unauthenticatedRole
    })

Possible Solution

I think the way to fix this is that if you create an identity pool without specifying roles it actually leaves them empty rather than generating default roles. This document tells me this should be possible as the attachment is something completely separate.

Why this is important is that I want to add constraints to my roles that only allow those role to be assumed by this specific identity pool. To do that, I need a reference to the identity pool. So for this to work you have to do things in this order:

  1. Create the identity pool with no attached roles
  2. Create the authorized and unauthorized roles with a trust policy constrained to that identity pool
  3. Attach these roles to the identity pool

Additional Information/Context

My versions of the pieces I am using from packages.json are:

    "@aws-cdk/aws-apigatewayv2-alpha": "^2.56.0-alpha.0",
    "@aws-cdk/aws-apigatewayv2-authorizers-alpha": "^2.56.0-alpha.0",
    "@aws-cdk/aws-apigatewayv2-integrations-alpha": "^2.56.0-alpha.0",
    "@aws-cdk/aws-cognito-identitypool-alpha": "^2.56.0-alpha.0",
    "aws-cdk": "2.56.0",
    "aws-cdk-lib": "2.56.0",

CDK CLI Version

2.56.0 (build 1485f48)

Framework Version

2.56.0-alpha.0

Node.js Version

v16.18.0

OS

Windows 10

Language

Typescript

Language Version

4.9.3

Other information

    "aws-cdk": "2.56.0",
    "aws-cdk-lib": "2.56.0",

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 9
  • Comments: 20 (4 by maintainers)

Most upvoted comments

I tried to split the stack in AUTH vs AUTHZ trying to create the roles and the mapping later but there are other failures there too.

The method to initialized a pool reference don’t work when the identityPoolId is a token

identityPool: IdentityPool.fromIdentityPoolId(
    this,
    "IdentityPool",
    props.identityPoolId,
),

this throws an error in this line https://github.com/aws/aws-cdk/blob/9b2573b32e8535d3db21f07647f099c9e01eb292/packages/%40aws-cdk/aws-cognito-identitypool/lib/identitypool.ts#L334

/Volumes/workplace/performance-dashboard-on-aws/cdk/node_modules/@aws-cdk/aws-cognito-identitypool-alpha/lib/identitypool.ts:334
    if (!(idParts.length === 2)) throw new Error('Invalid Identity Pool Id: Identity Pool Ids must follow the format <region>:<id>');
                                       ^
Error: Invalid Identity Pool Id: Identity Pool Ids must follow the format <region>:<id>
    at Function.fromIdentityPoolArn (/Volumes/workplace/performance-dashboard-on-aws/cdk/node_modules/@aws-cdk/aws-cognito-identitypool-alpha/lib/identitypool.ts:334:40)
    at new AuthorizationStack (/Volumes/workplace/performance-dashboard-on-aws/cdk/lib/authz-stack.ts:42:43)
    at Object.<anonymous> (/Volumes/workplace/performance-dashboard-on-aws/cdk/bin/main.ts:39:15)
    at Module._compile (node:internal/modules/cjs/loader:1155:14)
    at Module.m._compile (/Volumes/workplace/performance-dashboard-on-aws/cdk/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1209:10)
    at Object.require.extensions.<computed> [as .ts] (/Volumes/workplace/performance-dashboard-on-aws/cdk/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:1033:32)
    at Function.Module._load (node:internal/modules/cjs/loader:868:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)

Yes I as well want to grant different permissions to users depending on what cognito group they belong to. I assume that must be a somewhat common use case.

Groups are a little different. You specify groups and their associated roles as attributes of the group in the userpool. What we’re talking about here is the default roles for authenticated and unauthenticated users.

I want my users to use the default authenticated role, but to grant them additional privileges if in certain groups. I don’t need the unauthenticated role at the moment for my use case but it’s not an issue if it exists. The issue that I have is with trying to add role mappings. Something along the lines of:

    identityPool.addRoleMappings(
    {
      // map admin users to admin role
      mappingKey: 'cognito', // internal dumb CDK thing if you want to use a CDK token as a key
      providerUrl: IdentityPoolProviderUrl.userPool(`${userPoolProviderId}:${webClient.userPoolClientId}`), // allowed for clients using the web client
      resolveAmbiguousRoles: true, // does something
      rules: [
        // JWT claim -> IAM role mappings
        {
          // if group matches admin
          claim: 'cognito:groups',
          matchType: RoleMappingMatchType.CONTAINS,
          claimValue: ADMIN_ROLE,

          // use this role
          mappedRole: adminRole,
        },
      ],
    }
  );

Trying to interpret how to apply what’s mentioned in the docs here in CDK

I get the same error mentioned above:

us-east-1:09cbad3f-99be-4caf-ab51-584475ea29d2 already exists in stack

I believe because it’s a key in a mapping which has been already defined when the default auth/unauth roles are created automatically. There doesn’t seem to be a way to add or replace the default role mappings, even if I pass in [] explicitly in order to create my own mapping.

In my case, we have multiple roles at the application levels (unauthenticated, public, editor, admin). I would like to map a role for each one that only have access to what they need to do in our s3 bucket (read, put in specific paths). We do have a custom cognito claim (custom:roles = “” | “Public” | “Editor” | “Admin”) and if possible I would like to assign a role for each of those values

I think that all this role generation should be handled by the developer and not hidden inside the framework but anyway if we go that route this needs to be mentioned in the README somewhere.

I agree that it should all be handled by the developer. I’m going to go a step further and say it should be a requirement that it be handled by the developer. A README or documentation can provide the defaults as an example.

If we DO want to keep default roles, there should be a way to attach your own policies to them, inline or otherwise. But I think it’s simpler to just say “create your own roles, then your identity pool referencing those roles.”