CloudFormation で Cognito

# これまで

[Amazon Cognito](https://aws.amazon.com/jp/cognito/) は [AWS CloudFormation](https://aws.amazon.com/jp/cloudformation/) でのリソース作成と管理に対応しておらず、リソース作成を自動化したい場合は [AWS CLI](https://aws.amazon.com/jp/cli/) や [AWS SDK](https://aws.amazon.com/jp/tools/#sdk) を使った自作スクリプトを使う必要がありました。

しかし、この方法で冪等性を担保するのは難しく、 [CloudFormation Custom Resource](https://github.com/binoculars/aws-cloudformation-cognito-identity-pool) を作った猛者もいました。

また、 [IAM Role の Assume Policy に Identity Pool の ID を含めないといけない](http://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/iam-roles.html)点が特に面倒でした。

# これから

2017年4月28日、 Cognito の各種リソースが CloudFormation で作成・管理できるようになりました。

- [Amazon Cognito Now Supported by AWS CloudFormation](https://aws.amazon.com/jp/about-aws/whats-new/2017/04/amazon-cognito-now-supported-by-aws-cloudformation/)
- [AWS::Cognito::IdentityPool - AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypool.html)
- [AWS::Cognito::IdentityPoolRoleAttachment - AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-identitypoolroleattachment.html)
- [AWS::Cognito::UserPool - AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html)
- [AWS::Cognito::UserPoolClient - AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolclient.html)
- [AWS::Cognito::UserPoolGroup - AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolgroup.html)
- [AWS::Cognito::UserPoolUser - AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpooluser.html)
- [AWS::Cognito::UserPoolUserToGroupAttachment - AWS CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpoolusertogroupattachment.html)

これが待ち望んでいたものです。

User Pool 内のユーザーやグループまでリソースとして用意されているのが面白いですね。

IAM Role も同じスタック内で作ってしまえば Assume Policy の中で Identity Pool の ID も参照できるので、手動で ID をコピペする手間が省けて最高です。

## テンプレート例

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "Example template including Cognito Identity Pool and User Pool."
Parameters:
  EmailIdentityArn:
    Type: String
Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName: 
        Fn::Join:
          - ""
          - - Ref: AWS::StackName
            - Users
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: false
      AliasAttributes:
      - email
      - preferred_username
      AutoVerifiedAttributes:
      - email
      EmailConfiguration:
        SourceArn:
          Ref: EmailIdentityArn
      Policies:
        PasswordPolicy:
          MinimumLength: 8
          RequireLowercase: true
          RequireNumbers: true
          RequireSymbols: false
          RequireUppercase: true
      Schema:
      - Name: email
        AttributeDataType: String
        DeveloperOnlyAttribute: false
        Mutable: true
        Required: true
      - Name: preferred_username
        AttributeDataType: String
        DeveloperOnlyAttribute: false
        Mutable: true
        Required: false
  UserPoolClient:
    Type: AWS::Cognito::UserPoolClient
    Properties:
      ClientName: 
        Fn::Join:
          - ""
          - - Ref: AWS::StackName
            - Users-client
      GenerateSecret: false
      RefreshTokenValidity: 7
      UserPoolId:
        Ref: UserPool
  IdentityPool:
    Type: AWS::Cognito::IdentityPool
    Properties:
      AllowUnauthenticatedIdentities: true
      IdentityPoolName: 
        Fn::Join:
          - ""
          - - Ref: AWS::StackName
            - Users
      CognitoIdentityProviders:
      - ClientId:
          Ref: UserPoolClient
        ProviderName:
          Fn::Join:
          - ""
          - - cognito-idp.
            - Ref: "AWS::Region"
            - .amazonaws.com/
            - Ref: UserPool
  UnauthenticatedPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action:
          - mobileanalytics:PutEvents
          - cognito-sync:*
          Resource:
          - "*"
  UnauthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action: "sts:AssumeRoleWithWebIdentity"
          Principal:
            Federated: cognito-identity.amazonaws.com
          Condition:
            StringEquals:
              "cognito-identity.amazonaws.com:aud":
                Ref: IdentityPool
            ForAnyValue:StringLike:
              "cognito-identity.amazonaws.com:amr": unauthenticated
      ManagedPolicyArns:
      - Ref: UnauthenticatedPolicy
  AuthenticatedPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action:
          - mobileanalytics:PutEvents
          - cognito-sync:*
          - cognito-identity:*
          Resource:
          - "*"
  AuthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
        - Effect: Allow
          Action: "sts:AssumeRoleWithWebIdentity"
          Principal:
            Federated: cognito-identity.amazonaws.com
          Condition:
            StringEquals:
              "cognito-identity.amazonaws.com:aud":
                Ref: IdentityPool
            ForAnyValue:StringLike:
              "cognito-identity.amazonaws.com:amr": authenticated
      ManagedPolicyArns:
      - Ref: AuthenticatedPolicy
  RoleAttachment:
    Type: AWS::Cognito::IdentityPoolRoleAttachment
    Properties:
      IdentityPoolId:
        Ref: IdentityPool
      Roles:
        unauthenticated:
          Fn::GetAtt:
          - UnauthenticatedRole
          - Arn
        authenticated:
          Fn::GetAtt:
          - AuthenticatedRole
          - Arn
Outputs:
  UserPool:
    Value:
      Ref: UserPool
  UserPoolClient:
    Value:
      Ref: UserPoolClient
  IdentityPool:
    Value:
      Ref: IdentityPool
  UnauthenticatedRole:
    Value:
      Ref: UnauthenticatedRole
  AuthenticatedRole:
    Value:
      Ref: AuthenticatedRole
```

## その他の活用方法

Serverless Framework は CloudFormation のリソーステンプレートを埋め込めるので、 User Pool のトリガーで使う Lambda ファンクションを同時作成するのにうってつけです。

- [Serverless Framework - AWS Lambda Guide - AWS Infrastructure Resources](https://serverless.com/framework/docs/providers/aws/guide/resources/)

その場合は [AWS::Cognito::UserPool の LambdaConfig プロパティ](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cognito-userpool.html#cfn-cognito-userpool-lambdaconfig) を指定してトリガーを設定します。また、 [AWS::Lambda::Permission](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-permission.html) も忘れずに作る必要があります（`Principal` は `cognito-idp.amazonaws.com`）。