Özgür İnce
Özgür İnce

Reputation: 51

AWS Cloudformation can not create stack when AWS::Cognito::IdentityPoolRoleAttachment resource has RoleMappings attribute

I'm trying to create my cognito resources through cloudformation. Below template works just fine;

AWSTemplateFormatVersion: 2010-09-09
Resources:
  CognitoAuthRole:
    Type: 'AWS::IAM::Role'
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Federated: cognito-identity.amazonaws.com
            Action:
              - 'sts:AssumeRoleWithWebIdentity'
            Condition:
              StringEquals:
                'cognito-identity.amazonaws.com:aud':
                  Ref: CognitoIdentityPool
              'ForAnyValue:StringLike':
                'cognito-identity.amazonaws.com:amr': authenticated
  CognitoUserPool:
    Type: 'AWS::Cognito::UserPool'
    Properties:
      UsernameAttributes:
        - email
      AutoVerifiedAttributes:
        - email
  CognitoUserPoolClient:
    Type: 'AWS::Cognito::UserPoolClient'
    Properties:
      UserPoolId:
        Ref: CognitoUserPool
      ExplicitAuthFlows:
        - ADMIN_NO_SRP_AUTH
      GenerateSecret: false
  CognitoIdentityPool:
    Type: 'AWS::Cognito::IdentityPool'
    Properties:
      AllowUnauthenticatedIdentities: true
      CognitoIdentityProviders:
        - ClientId:
            Ref: CognitoUserPoolClient
          ProviderName:
            'Fn::GetAtt':
              - CognitoUserPool
              - ProviderName
  CognitoIdentityPoolRoles:
    Type: 'AWS::Cognito::IdentityPoolRoleAttachment'
    Properties:
      IdentityPoolId:
        Ref: CognitoIdentityPool
      Roles:
        authenticated:
          'Fn::GetAtt':
            - CognitoAuthRole
            - Arn

But when I add RoleMappings attribute to CognitoIdentityPoolRoles resource, Cloudformation returns an error and can not create the stack. Modified resource is below;

  CognitoIdentityPoolRoles:
    Type: 'AWS::Cognito::IdentityPoolRoleAttachment'
    Properties:
      IdentityPoolId:
        Ref: CognitoIdentityPool
      Roles:
        authenticated:
          'Fn::GetAtt':
            - CognitoAuthRole
            - Arn
      RoleMappings:
        AmbiguousRoleResolution: Deny
        Type: Rules
        RulesConfiguration:
          Rules:
            - Claim: 'custom:role'
              MatchType: Equals
              Value: viewer
              RoleARN:
                'Fn::GetAtt':
                  - CognitoAuthRole
                  - Arn
            - Claim: 'custom:role'
              MatchType: Equals
              Value: editor
              RoleARN:
                'Fn::GetAtt':
                  - CognitoAuthRole
                  - Arn

As you can see above RoleMappings type is Rules. You could try with Token parameter and the outcome doesn't change.

  CognitoIdentityPoolRoles:
    Type: 'AWS::Cognito::IdentityPoolRoleAttachment'
    Properties:
      IdentityPoolId:
        Ref: CognitoIdentityPool
      Roles:
        authenticated:
          'Fn::GetAtt':
            - CognitoAuthRole
            - Arn
      RoleMappings:
        AmbiguousRoleResolution: Deny
        Type: Token

Unfortunately error message doesn't give any clue, I can not make any progress and stuck in this stage.

Status  Type    Logical ID  Status Reason
CREATE_FAILED   AWS::Cognito::IdentityPoolRoleAttachment    CognitoIdentityPoolRoles    Internal Failure

How can I make IdentityPoolRoleAttachment with RoleMappings work?

Upvotes: 4

Views: 1659

Answers (1)

I've got the same issue, and unfortunately as i could see the RoleMappings are not supported yet into CloudFormation, so we ever will catch this 'Internal Failure'. But there is some workarounds that you can do to solve your problem. In my case I've used the boto3 library to invoke IdentityPool updates inside a Lambda Function and I've used the Severless Framework, but the same purpose could be did with SAM or another CloudFormation stack framework. So, I did these steps using 2 separated stacks:

  1. Create First stack including all your Cognito Resources (UserPool, UserPoolClient, IdentityPool) and IamRoles that you will assign them and on the Outputs section, Export the necessary IDs and ARNs of your Resources to be used on the next stack.

service: cognito-template

provider:
  name: aws
  stage: dev
  region: us-east-1
  stackName: cognito-template-${self:provider.stage}-resources

custom:
  system:
    name: myapp
    cognitoclientname: MyAppClient

resources:
  Resources:
    # ## ## ## ## ## ## ## ## ## ## ## ## ## Definicao de Usuários Cognito UserPool ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## # 
    UserPool:
      Type: AWS::Cognito::UserPool
      Properties:
        UserPoolName: ${self:custom.system.name}userpool
        AdminCreateUserConfig: 
          AllowAdminCreateUserOnly: True
          UnusedAccountValidityDays: 30
        EmailVerificationMessage: Clique no link abaixo para verificar seu endereço de e-mail. {####}
        EmailVerificationSubject: Seu link de verificação
        MfaConfiguration: OFF
        Policies: 
          PasswordPolicy:
            MinimumLength: 8
            RequireLowercase: false
            RequireNumbers: false
            RequireSymbols: false
            RequireUppercase: false
        Schema: 
          - AttributeDataType: String
            DeveloperOnlyAttribute: false
            Mutable: true
            Name: name
            Required: true
          - AttributeDataType: String
            DeveloperOnlyAttribute: false
            Mutable: true
            Name: family_name
            Required: true
          - AttributeDataType: String
            DeveloperOnlyAttribute: false
            Mutable: true
            Name: email
            Required: true
          - AttributeDataType: String
            DeveloperOnlyAttribute: false
            Mutable: true
            Name: phone_number
            Required: true
          - AttributeDataType: String
            DeveloperOnlyAttribute: false
            Mutable: true
            Name: gender
            Required: true
          - AttributeDataType: String
            DeveloperOnlyAttribute: false
            Mutable: true
            Name: permission
            Required: false
        UsernameAttributes: 
          - email
          - phone_number
    # ## ## ## ## ## ## ## ## ## ## ## ## ## Client Cognito ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## # 
    AppUserPoolClient:
      Type: AWS::Cognito::UserPoolClient
      Properties:
          ClientName: ${self:custom.system.cognitoclientname}
          ExplicitAuthFlows: 
            - ADMIN_NO_SRP_AUTH
            - USER_PASSWORD_AUTH
          GenerateSecret: false
          RefreshTokenValidity: 1
          UserPoolId: !Ref UserPool
    # ## ## ## ## ## ## ## ## ## ## ## ## ## Provedor de Identidade Cognito ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## # 
    AppIdentityPool:
      Type: AWS::Cognito::IdentityPool
      Properties:
        IdentityPoolName: ${self:custom.system.name}identitypool
        AllowUnauthenticatedIdentities: false
        CognitoIdentityProviders: 
          - ClientId: !Ref AppUserPoolClient
            ProviderName: !GetAtt UserPool.ProviderName
    AppIdentitiesRolesAttachment:
      Type: AWS::Cognito::IdentityPoolRoleAttachment
      DependsOn:
        - AppIdentityPool
        - CognitoAuthorizedRole
        - CognitoUnAuthorizedRole
      Properties:
        IdentityPoolId: !Ref AppIdentityPool
        Roles: 
          authenticated: !GetAtt CognitoAuthorizedRole.Arn
          unauthenticated: !GetAtt CognitoUnAuthorizedRole.Arn
    CognitoAuthorizedRole:
      Type: "AWS::IAM::Role"
      Properties:
        AssumeRolePolicyDocument: 
          Version: "2012-10-17"
          Statement:
            - Effect: "Allow"
              Principal: 
                Federated: "cognito-identity.amazonaws.com"
              Action: 
                - "sts:AssumeRoleWithWebIdentity"
              Condition:
                StringEquals: 
                  "cognito-identity.amazonaws.com:aud": !Ref AppIdentityPool
                "ForAnyValue:StringLike":
                  "cognito-identity.amazonaws.com:amr": authenticated
        Policies:
          - PolicyName: "CognitoAuthorizedPolicy"
            PolicyDocument: 
              Version: "2012-10-17"
              Statement: 
                - Effect: "Allow"
                  Action:
                    - "mobileanalytics:PutEvents"
                    - "cognito-sync:*"
                    - "cognito-identity:*"
                  Resource: "*"
                - Effect: "Allow"
                  Action:
                    - "lambda:InvokeFunction"
                  Resource: "*"
    CognitoUnAuthorizedRole:
      Type: "AWS::IAM::Role"
      Properties:
        AssumeRolePolicyDocument: 
          Version: "2012-10-17"
          Statement:
            - Effect: "Allow"
              Principal: 
                Federated: "cognito-identity.amazonaws.com"
              Action: 
                - "sts:AssumeRoleWithWebIdentity"
              Condition:
                StringEquals: 
                  "cognito-identity.amazonaws.com:aud": !Ref AppIdentityPool
                "ForAnyValue:StringLike":
                  "cognito-identity.amazonaws.com:amr": unauthenticated
        Policies:
          - PolicyName: "CognitoUnauthorizedPolicy"
            PolicyDocument: 
              Version: "2012-10-17"
              Statement: 
                - Effect: "Allow"
                  Action:
                    - "mobileanalytics:PutEvents"
                    - "cognito-sync:*"
                  Resource: "*"
    AdministradorRole:
      Type: "AWS::IAM::Role"
      Properties:
        AssumeRolePolicyDocument: 
          Version: "2012-10-17"
          Statement:
            - Effect: "Allow"
              Principal: 
                Federated: "cognito-identity.amazonaws.com"
              Action: 
                - "sts:AssumeRoleWithWebIdentity"
              Condition:
                StringEquals: 
                  "cognito-identity.amazonaws.com:aud": !Ref AppIdentityPool
                "ForAnyValue:StringLike":
                  "cognito-identity.amazonaws.com:amr": authenticated
        Policies:
          - PolicyName: "CognitoAdministradorPolicy"
            PolicyDocument: 
              Version: "2012-10-17"
              Statement: 
                - Effect: "Allow"
                  Action:
                    - "mobileanalytics:PutEvents"
                    - "cognito-sync:*"
                  Resource: "*"
    GerenciadorRole:
      Type: "AWS::IAM::Role"
      Properties:
        AssumeRolePolicyDocument: 
          Version: "2012-10-17"
          Statement:
            - Effect: "Allow"
              Principal: 
                Federated: "cognito-identity.amazonaws.com"
              Action: 
                - "sts:AssumeRoleWithWebIdentity"
              Condition:
                StringEquals: 
                  "cognito-identity.amazonaws.com:aud": !Ref AppIdentityPool
                "ForAnyValue:StringLike":
                  "cognito-identity.amazonaws.com:amr": authenticated
        Policies:
          - PolicyName: "CognitoAdministradorPolicy"
            PolicyDocument: 
              Version: "2012-10-17"
              Statement: 
                - Effect: "Allow"
                  Action:
                    - "mobileanalytics:PutEvents"
                    - "cognito-sync:*"
                  Resource: "*"
                - Effect: "Allow"
                  Action:
                    - "s3:GetObject"
                    - "s3:PutObject"
                  Resource: 
                    - "arn:aws:s3:::${self:custom.system.name}/public/*"
  # ## ## ## ## ## ## ## ## ## ## ## ## ## IAM Permission to lambda script execute update into IdentityPoolRoleMappings ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## #                   
    MigrationScriptRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument: 
          Version: "2012-10-17"
          Statement:
            - Effect: "Allow"
              Principal: 
                Service: 
                  - lambda.amazonaws.com
              Action: 
                - "sts:AssumeRole"
        Policies:
          - PolicyName: "MigrationScriptPolicy"
            PolicyDocument: 
              Version: "2012-10-17"
              Statement: 
                - Effect: "Allow"
                  Action: 
                    - "cognito-idp:*"
                    - "cognito-identity:*"
                    - "iam:*"
                  Resource: "*"
  Outputs:
    UserPoolId:
      Value: !Ref UserPool
      Export:
        Name: "UserPool::Id"
    UserPoolArn:
      Value: !GetAtt UserPool.Arn
      Export:
        Name: "UserPool::Arn"
    UserPoolClientId:
      Value: !Ref AppUserPoolClient
      Export:
        Name: "AppUserPoolClient::Id"
    AppIdentityPoolId:
      Value: !Ref AppIdentityPool
      Export:
        Name: "AppIdentityPool::Id"
    AdministradorRoleArn:
      Value: !GetAtt AdministradorRole.Arn
      Export:
        Name: "AdministradorRole::Arn"
    GerenciadorRoleArn:
      Value: !GetAtt GerenciadorRole.Arn
      Export:
        Name: "GerenciadorRole::Arn"
    MigrationScriptRoleArn:
      Value: !GetAtt MigrationScriptRole.Arn
      Export:
        Name: "MigrationScriptRole::Arn"
  1. Create second stack including the lambda function that will invoke update_user_pool from boto3 library. This function must receive the exported values of the first one stack and attached them into the enviroment variabels to be used when the function will invoke.

service: cognito-template

provider:
  name: aws
  stage: dev
  region: us-east-1
  stackName: cognito-template-${self:provider.stage}-functions

functions:
  migration-script:
    handler: lambda_function.handler
    runtime: python3.6
    role: 
      Fn::ImportValue: !Sub MigrationScriptRole::Arn
    environment:
      USER_POOL_REGION: us-east-1 # here you can change to you preferred region if you want
      USER_POOL_ID: 
        Fn::ImportValue: !Sub UserPool::Id
      USER_POOL_CLIENT_ID:
        Fn::ImportValue: !Sub AppUserPoolClient::Id
      IDENTITY_POOL_ID:
        Fn::ImportValue: !Sub AppIdentityPool::Id
      ADMINISTRADOR_ROLE_ARN:
        Fn::ImportValue: !Sub AdministradorRole::Arn
      GERENCIADOR_ROLE_ARN:
        Fn::ImportValue: !Sub GerenciadorRole::Arn
  1. Finally, the code of the lambda function to execute an update via boto3 call, for that, i've used the python3.6, but can use Node(must see the boto3 docs for Node)

    
    import boto3
    import os

    def handler(event, context): setup_cognito() return event def setup_cognito(): define_cognito_attributes() create_cognito_identity_roles() def create_cognito_identity_roles(): user_pool_region = os.environ['USER_POOL_REGION'] user_pool_id = os.environ['USER_POOL_ID'] user_pool_client_id = os.environ['USER_POOL_CLIENT_ID'] identity_pool_id = os.environ['IDENTITY_POOL_ID']
    administrador_role = os.environ['ADMINISTRADOR_ROLE_ARN'] gerenciador_role = os.environ['GERENCIADOR_ROLE_ARN'] client_identity = boto3.client('cognito-identity') client_idp = boto3.client('cognito-idp') response = client_identity.get_identity_pool_roles(IdentityPoolId=identity_pool_id) identity_provider = "cognito-idp.{}.amazonaws.com/{}:{}".format(user_pool_region, user_pool_id, user_pool_client_id) options = { 'IdentityPoolId': response['IdentityPoolId'], 'Roles': response['Roles'], 'RoleMappings': { identity_provider: { 'Type': 'Rules', 'AmbiguousRoleResolution': 'AuthenticatedRole', 'RulesConfiguration': { 'Rules': [ { 'Claim': 'custom:permission', 'MatchType': 'Equals', 'Value': 'ADMNISTRADOR', 'RoleARN': administrador_role }, { 'Claim': 'custom:permission', 'MatchType': 'Equals', 'Value': 'GERENCIADOR', 'RoleARN': gerenciador_role } ] } } } } response = client_identity.set_identity_pool_roles(IdentityPoolId=options['IdentityPoolId'], Roles=options['Roles'], RoleMappings=options['RoleMappings']) def define_cognito_attributes(): user_pool_id = os.environ['USER_POOL_ID'] user_pool_client_id = os.environ['USER_POOL_CLIENT_ID'] client = boto3.client('cognito-idp') response = client.update_user_pool_client( UserPoolId=user_pool_id, ClientId=user_pool_client_id, WriteAttributes=[ 'custom:permission', 'phone_number', 'email', 'name', 'family_name', 'gender' ] )

After create, the lambda function can be invoked by CLI or WEB 'Test'Button and then your role mappings will be assigned to you IdentityPool as you wish.

Hope it can help you! (Y)

Upvotes: 2

Related Questions