Skip to content

Instantly share code, notes, and snippets.

@nicdk
Created December 12, 2018 08:24
Show Gist options
  • Save nicdk/5e4429e6c93a0cd743ca9ab1e7b3192b to your computer and use it in GitHub Desktop.
Save nicdk/5e4429e6c93a0cd743ca9ab1e7b3192b to your computer and use it in GitHub Desktop.

Amazon Cognito セットアップ % AWS CloudFormation + IAM

  • 日付: 2018/11/26

概要

Amazon Cognito の構築を AWS CloudFormation(cfn) を使って行ってみる。

メモ

Amazon Cognito

AWS CloudFromation (CFn)

CloudFormation の関数について。

「」抜粋引用

Fn::Join

組み込み関数 Fn::Join は、一連の値を特定の区切り文字で区切って 1 つの値に追加します。区切り文字が空の文字列の場合、一連の値は区切り文字を使用することなく連結されます。

宣言(YAML)

Fn::Join: [ delimiter, [ comma-delimited list of values ] ]

パラメータ
区切り記号
値を区切る区切り文字を指定する値。区切り文字は値の間にのみ挿入されます。これは最後の値を終了しません。 ListOfValues
結合する値のリスト。

戻り値
結合された文字列。

Ref

組み込み関数 Ref は、指定したパラメーターまたはリソースの値を返します。

  • パラメーターの論理名を指定すると、それはパラメーターの値を返します。
  • リソースの論理名を指定すると、それはそのリソースを参照するために通常使用できる値を返します

宣言(YAML)

Ref: logicalName

パラメータ logicalName 参照解除するパラメーターまたはリソースの論理名。

戻り値 リソースの物理 ID またはパラメーターの値。

AWS Identity and Access Management (IAM)

Cognito に関連するロールの説明

CloudFormation に関連するロールの説明

ー AWS Identity and Access Management によるアクセスの制御 - AWS CloudFormation https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-iam-template.html

ベストプラクティスほか

ほか。

ツール

cfn-python-lint

CloudFormation template のチェッカ

CloudFormation ドリフト検出

差分を確認できる。

AWS CLI

コマンドラインツール

プロファイル

プロファイルは aws configure (= ~/.aws/credential)の代わりに環境変数を使っている。 環境変数は direnv で設定。

% val "$(direnv hook zsh)"
direnv: loading .envrc
direnv: export +AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY

aws cognito-IdentityPool

Cognito のコマンドラインツール

describe-user-pool

% aws cognito-idp describe-user-pool --user-pool-id=ap-northeast-1_iMarCxluB
USERPOOL	arn:aws:cognito-idp:ap-northeast-1:410408491513:userpool/ap-northeast-1_iMarCxluB	1543223674.289	1	ap-northeast-1_iMarCxluB	1543223674.289	OFFaws-cfn-demo
ADMINCREATEUSERCONFIG	False	7
AUTOVERIFIEDATTRIBUTES	email
PASSWORDPOLICY	8	True	True	True	True
SCHEMAATTRIBUTES	String	False	False	sub	True
STRINGATTRIBUTECONSTRAINTS	2048	1
SCHEMAATTRIBUTES	String	False	True	name	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	given_name	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	family_name	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	middle_name	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	nickname	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	preferred_username	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	profile	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	picture	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	website	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	email	True
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	Boolean	False	True	email_verified	False
SCHEMAATTRIBUTES	String	False	True	gender	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	birthdate	False
STRINGATTRIBUTECONSTRAINTS	10	10
SCHEMAATTRIBUTES	String	False	True	zoneinfo	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	locale	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	String	False	True	phone_number	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	Boolean	False	True	phone_number_verified	False
SCHEMAATTRIBUTES	String	False	True	address	False
STRINGATTRIBUTECONSTRAINTS	2048	0
SCHEMAATTRIBUTES	Number	False	True	updated_at	False
NUMBERATTRIBUTECONSTRAINTS	0
VERIFICATIONMESSAGETEMPLATE	CONFIRM_WITH_CODE

aws コマンドの標準オプション --output で json を指定すると json 形式で得られる。

aws cognito-idp describe-user-pool --user-pool-id ap-northeast-1_60F2FA9PW --output json | jq .UserPool.Policies
{
  "PasswordPolicy": {
    "MinimumLength": 8,
    "RequireUppercase": true,
    "RequireLowercase": true,
    "RequireNumbers": true,
    "RequireSymbols": false
  }
}

aws cloudformation

CloudFormation のコマンドラインツール

validate-template

% aws cloudformation validate-template --template-body  file://aws-cfn-cognito-template.yaml
The following resource(s) require capabilities: [AWS::IAM::ManagedPolicy]	Example template including Cognito Identity Pool and User Pool.
CAPABILITIES	CAPABILITY_IAM
PARAMETERS	False	EmailIdentityArn

IAM アクセスアドバイザー

グループ、ユーザー、ロール、ポリシーについて、付与されているアクセス権限と最後にアクセスされた時間が確認できる。

AWS CloudTrail

アクセス権限が無く失敗しているときの調査できる。

想定どおり動かないときは errorCode, errorMessage などから原因を追う。

検証: CloudFormation で Cognito

Cognito CloudFormation テンプレート

CloudFormationの Cognito リソースについて。

{
  "Type" : "AWS::Cognito::UserPool",
  "Properties" : {
    "AdminCreateUserConfig" : AdminCreateUserConfig,
    "AliasAttributes" : [ String ],
    "AutoVerifiedAttributes" : [ String ],
    "DeviceConfiguration" : DeviceConfiguration,
    "EmailConfiguration" : EmailConfiguration,
    "EmailVerificationMessage" : String,
    "EmailVerificationSubject" : String,
    "LambdaConfig" : LambdaConfig,
    "MfaConfiguration" : String,
    "Policies" : Policies,
    "Schema" : [ SchemaAttribute ],
    "SmsAuthenticationMessage" : String,
    "SmsConfiguration" : SmsConfiguration,
    "SmsVerificationMessage" : String,
    "UsernameAttributes" : [ String ],
    "UserPoolName" : String,
    "UserPoolTags" : { 文字列: 文字列, ... }
  }
}
{
  "Type" : "AWS::Cognito::UserPoolClient",
  "Properties" : {
    "ClientName" : String,
    "ExplicitAuthFlows" : [ String, ... ],
    "GenerateSecret" : Boolean,
    "ReadAttributes" : [ String, ... ],
    "RefreshTokenValidity" : Integer,
    "UserPoolId" : String,
    "WriteAttributes" : [ String, ... ]
  }
}

CloudFormation での登録動作の確認

こちらのものを追跡試験した。

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

結果、うまくいかなかった。

  • Parameter の EmailIdentityArn の内容が把握できなかった。
  • 作成時の Cognito の IdentityPool のエラーが修正できなかった
  • 作成時の IAM の UnAuthenticatedRole のエラーが修正できなかった。

CloudFormation 登録向け変更

前のもの変更して再度実行。 UserPool と UserPoolClient のみに変更した。

実行できたものはこちら。

AWSTemplateFormatVersion: "2010-09-09"
Description: "Example template including Cognito Identity Pool and User Pool."
Resources:
  UserPool:
    Type: AWS::Cognito::UserPool
    Properties:
      UserPoolName:
        Fn::Join:
          - ""
          - - Ref: AWS::StackName
            - Users
      AdminCreateUserConfig:
        AllowAdminCreateUserOnly: false
      AliasAttributes:
      - email
      - preferred_username
      AutoVerifiedAttributes:
      - email
      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
Outputs:
  UserPool:
    Value:
      Ref: UserPool
  UserPoolClient:
    Value:
      Ref: UserPoolClient

上記の template を実行した結果 CloudFormation に cfn-cognito-template-06 stack を作成した。

登録後にtemplateタブから template の言語をJSON にしたものはこちら。

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Example template including Cognito Identity Pool and User Pool.",
    "Resources": {
        "UserPool": {
            "Type": "AWS::Cognito::UserPool",
            "Properties": {
                "UserPoolName": {
                    "Fn::Join": [
                        "",
                        [
                            {
                                "Ref": "AWS::StackName"
                            },
                            "Users"
                        ]
                    ]
                },
                "AdminCreateUserConfig": {
                    "AllowAdminCreateUserOnly": false
                },
                "AliasAttributes": [
                    "email",
                    "preferred_username"
                ],
                "AutoVerifiedAttributes": [
                    "email"
                ],
                "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"
                }
            }
        }
    },
    "Outputs": {
        "UserPool": {
            "Value": {
                "Ref": "UserPool"
            }
        },
        "UserPoolClient": {
            "Value": {
                "Ref": "UserPoolClient"
            }
        }
    }
}

その結果、Cognito に cfn-cognito-template-06Users という UserPool が作成された。

CloudFormation で作成した UserPool

Cognito に CloudFormation で作成した UserPool stack cfn-cognito-template-06Users の内容確認。

% aws cognito-idp describe-user-pool --user-pool-id ap-northeast-1_60F2FA9PW --output json
{
    "UserPool": {
        "Id": "ap-northeast-1_60F2FA9PW",
        "Name": "cfn-cognito-template-06Users",
        "Policies": {
            "PasswordPolicy": {
                "MinimumLength": 8,
                "RequireUppercase": true,
                "RequireLowercase": true,
                "RequireNumbers": true,
                "RequireSymbols": false
            }
        },
        "LambdaConfig": {},
        "LastModifiedDate": 1543314244.84,
        "CreationDate": 1543314244.84,
        "SchemaAttributes": [
            {
                "Name": "sub",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": false,
                "Required": true,
                "StringAttributeConstraints": {
                    "MinLength": "1",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "name",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "given_name",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "family_name",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "middle_name",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "nickname",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "preferred_username",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "profile",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "picture",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "website",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "email",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": true,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "email_verified",
                "AttributeDataType": "Boolean",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false
            },
            {
                "Name": "gender",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "birthdate",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "10",
                    "MaxLength": "10"
                }
            },
            {
                "Name": "zoneinfo",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "locale",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "phone_number",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "phone_number_verified",
                "AttributeDataType": "Boolean",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false
            },
            {
                "Name": "address",
                "AttributeDataType": "String",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "StringAttributeConstraints": {
                    "MinLength": "0",
                    "MaxLength": "2048"
                }
            },
            {
                "Name": "updated_at",
                "AttributeDataType": "Number",
                "DeveloperOnlyAttribute": false,
                "Mutable": true,
                "Required": false,
                "NumberAttributeConstraints": {
                    "MinValue": "0"
                }
            }
        ],
        "AutoVerifiedAttributes": [
            "email"
        ],
        "AliasAttributes": [
            "email",
            "preferred_username"
        ],
        "VerificationMessageTemplate": {
            "DefaultEmailOption": "CONFIRM_WITH_CODE"
        },
        "MfaConfiguration": "OFF",
        "EstimatedNumberOfUsers": 0,
        "EmailConfiguration": {},
        "UserPoolTags": {},
        "AdminCreateUserConfig": {
            "AllowAdminCreateUserOnly": false,
            "UnusedAccountValidityDays": 7
        },
        "Arn": "arn:aws:cognito-idp:ap-northeast-1:410408491513:userpool/ap-northeast-1_60F2FA9PW"
    }
}

検証: AWS ユースケース Wild Rydes workshops

作成手順が5つのモジュールに分かれている。 モジュール1と2はCloudFormation templateがある。

CloudFormationの実行は 1, 2 ともに失敗した。 ワークショップの手順どおりに実行したところ、一部修正があったが完了できた。

作成したサイトの URL はこちら。

モジュール1 CloudFormation template

提供されている CloudFormation template を引用。

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Creates a static website using S3 for the Wild Rydes serverless web application workshop",
    "Parameters": {
        "BucketName": {
            "Type": "String",
            "Description": "The name for the bucket hosting your website, e.g. 'wildrydes-yourname'"
        },
        "CodeBucket": {
            "Type": "String",
            "Default": "wildrydes-ap-northeast-1",
            "Description": "S3 bucket containing the code deployed by this template"
        },
        "CodeKeyPrefix": {
            "Type": "String",
            "Default": "WebApplication/1_StaticWebHosting",
            "Description": "Key prefix for resources referenced from the CodeBucket"
        }
    },
    "Metadata": {
        "AWS::CloudFormation::Interface": {
            "ParameterGroups": [
                {
                    "Label": {
                        "default": "Website Configuration"
                    },
                    "Parameters": [
                        "BucketName"
                    ]
                },
                {
                    "Label": {
                        "default": "Advanced Configuration"
                    },
                    "Parameters": [
                        "CodeBucket",
                        "CodeKeyPrefix"
                    ]
                }
            ],
            "ParameterLabels": {
                "BucketName": {
                    "default": "Website Bucket Name"
                }
            }
        }
    },
    "Resources": {
        "WebsiteBucket": {
            "Properties": {
                "BucketName": {
                    "Ref": "BucketName"
                },
                "WebsiteConfiguration": {
                    "IndexDocument": "index.html"
                }
            },
            "Type": "AWS::S3::Bucket"
        },
        "WebsiteBucketPolicy": {
            "Properties": {
                "Bucket": {
                    "Ref": "WebsiteBucket"
                },
                "PolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": "*",
                            "Action": "s3:GetObject",
                            "Resource": {
                                "Fn::Sub": "arn:aws:s3:::${WebsiteBucket}/*"
                            }
                        }
                    ]
                }
            },
            "Type": "AWS::S3::BucketPolicy"
        },
        "WebsiteContent": {
            "Properties": {
                "ServiceToken": {
                    "Fn::GetAtt": [
                        "CopyS3ObjectsFunction",
                        "Arn"
                    ]
                },
                "SourceBucket": {
                    "Ref": "CodeBucket"
                },
                "SourcePrefix": {
                    "Fn::Sub": "${CodeKeyPrefix}/website/"
                },
                "Bucket": {
                    "Ref": "WebsiteBucket"
                }
            },
            "Type": "Custom::S3Objects"
        },
        "S3CopyRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "Path": "/wildrydes/",
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "lambda.amazonaws.com"
                            },
                            "Action": "sts:AssumeRole"
                        }
                    ]
                },
                "Policies": [
                    {
                        "PolicyName": "S3Access",
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Sid": "AllowLogging",
                                    "Effect": "Allow",
                                    "Action": [
                                        "logs:CreateLogGroup",
                                        "logs:CreateLogStream",
                                        "logs:PutLogEvents"
                                    ],
                                    "Resource": "*"
                                },
                                {
                                    "Sid": "SourceBucketReadAccess",
                                    "Effect": "Allow",
                                    "Action": [
                                        "s3:ListBucket",
                                        "s3:GetObject"
                                    ],
                                    "Resource": [
                                        {
                                            "Fn::Sub": "arn:aws:s3:::${CodeBucket}"
                                        },
                                        {
                                            "Fn::Sub": "arn:aws:s3:::${CodeBucket}/${CodeKeyPrefix}/*"
                                        }
                                    ]
                                },
                                {
                                    "Sid": "DestBucketWriteAccess",
                                    "Effect": "Allow",
                                    "Action": [
                                        "s3:ListBucket",
                                        "s3:GetObject",
                                        "s3:PutObject",
                                        "s3:PutObjectAcl",
                                        "s3:PutObjectVersionAcl",
                                        "s3:DeleteObject",
                                        "s3:DeleteObjectVersion",
                                        "s3:CopyObject"
                                    ],
                                    "Resource": [
                                        {
                                            "Fn::Sub": "arn:aws:s3:::${WebsiteBucket}"
                                        },
                                        {
                                            "Fn::Sub": "arn:aws:s3:::${WebsiteBucket}/*"
                                        }
                                    ]
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "CopyS3ObjectsFunction": {
            "Properties": {
                "Description": "Copies objects from a source S3 bucket to a destination",
                "Handler": "index.handler",
                "Runtime": "python2.7",
                "Role": {
                    "Fn::GetAtt": [
                        "S3CopyRole",
                        "Arn"
                    ]
                },
                "Timeout": 120,
                "Code": {
                    "ZipFile": "import os\nimport json\nimport cfnresponse\n\nimport boto3\nfrom botocore.exceptions import ClientError\nclient = boto3.client('s3')\n\nimport logging\nlogger = logging.getLogger()\nlogger.setLevel(logging.INFO)\n\ndef handler(event, context):\n  logger.info(\"Received event: %s\" % json.dumps(event))\n  source_bucket = event['ResourceProperties']['SourceBucket']\n  source_prefix = event['ResourceProperties'].get('SourcePrefix') or ''\n  bucket = event['ResourceProperties']['Bucket']\n  prefix = event['ResourceProperties'].get('Prefix') or ''\n\n  result = cfnresponse.SUCCESS\n\n  try:\n    if event['RequestType'] == 'Create' or event['RequestType'] == 'Update':\n      result = copy_objects(source_bucket, source_prefix, bucket, prefix)\n    elif event['RequestType'] == 'Delete':\n      result = delete_objects(bucket, prefix)\n  except ClientError as e:\n    logger.error('Error: %s', e)\n    result = cfnresponse.FAILED\n\n  cfnresponse.send(event, context, result, {})\n\n\ndef copy_objects(source_bucket, source_prefix, bucket, prefix):\n  paginator = client.get_paginator('list_objects_v2')\n  page_iterator = paginator.paginate(Bucket=source_bucket, Prefix=source_prefix)\n  for key in {x['Key'] for page in page_iterator for x in page['Contents']}:\n    dest_key = os.path.join(prefix, os.path.relpath(key, source_prefix))\n    if not key.endswith('/'):\n      print 'copy {} to {}'.format(key, dest_key)\n      client.copy_object(CopySource={'Bucket': source_bucket, 'Key': key}, Bucket=bucket, Key = dest_key)\n  return cfnresponse.SUCCESS\n\ndef delete_objects(bucket, prefix):\n  paginator = client.get_paginator('list_objects_v2')\n  page_iterator = paginator.paginate(Bucket=bucket, Prefix=prefix)\n  objects = [{'Key': x['Key']} for page in page_iterator for x in page['Contents']]\n  client.delete_objects(Bucket=bucket, Delete={'Objects': objects})\n  return cfnresponse.SUCCESS\n"
                }
            },
            "Type": "AWS::Lambda::Function"
        }
    },
    "Outputs": {
        "WebsiteURL": {
            "Value": {
                "Fn::GetAtt": [
                    "WebsiteBucket",
                    "WebsiteURL"
                ]
            }
        }
    }
}

こちらの実行を試したところ失敗した。以下のイベント出力が出ていた。

以下のドキュメントを元に原因調査。

case 1.

WebsiteContent
CREATE_FAILED
Failed to create resource. See the details in CloudWatch Log Stream: 2018/11/29/[$LATEST]c64242fcc9374bfeb18b5c88c170f25a

これは CloudFormation が対応していないリソースの生成をインラインで書いた Lambda Function でやらせる箇所でエラーがでている。

case2.

Custom Resource failed to stabilize in expected time

リソースの WebsiteContent のログにとのメッセージがあった。 こちらは先のCREATE_FAILDのイベントの「更新のロールバックの失敗」に該当する箇所になる。

モジュール2 CloudFormation template

引用する。

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Description": "Creates a Cognito User Pool for the Wild Rydes serverless web application workshop",
    "Parameters": {
        "WebsiteBucket": {
            "Type": "String",
            "Description": "The name for the bucket hosting your website, e.g. 'wildrydes-yourname.'"
        }
    },
    "Metadata": {
        "AWS::CloudFormation::Interface": {
            "ParameterGroups": [
                {
                    "Label": {
                        "default": "Module 1 Details"
                    },
                    "Parameters": [
                        "WebsiteBucket"
                    ]
                }
            ],
            "ParameterLabels": {
                "WebsiteBucket": {
                    "default": "Website Bucket Name"
                }
            }
        }
    },
    "Resources": {
        "UserPool": {
            "Type": "AWS::Cognito::UserPool",
            "Properties": {
                "UserPoolName": "WildRydes",
                "AliasAttributes": [
                    "email"
                ],
                "AutoVerifiedAttributes": [
                    "email"
                ]
            }
        },
        "UserPoolClient": {
            "Type": "AWS::Cognito::UserPoolClient",
            "Properties": {
                "ClientName": "WildRydesWeb",
                "UserPoolId": {
                    "Ref": "UserPool"
                },
                "GenerateSecret": false
            }
        },
        "UpdateConfig": {
            "Properties": {
                "ServiceToken": {
                    "Fn::GetAtt": [
                        "UpdateConfigFunction",
                        "Arn"
                    ]
                },
                "UserPool": {
                    "Ref": "UserPool"
                },
                "Client": {
                    "Ref": "UserPoolClient"
                },
                "Region": {
                    "Ref": "AWS::Region"
                },
                "Bucket": {
                    "Ref": "WebsiteBucket"
                }
            },
            "Type": "Custom::ConfigFile"
        },
        "CognitoConfigRole": {
            "Type": "AWS::IAM::Role",
            "Properties": {
                "Path": "/wildrydes/",
                "AssumeRolePolicyDocument": {
                    "Version": "2012-10-17",
                    "Statement": [
                        {
                            "Effect": "Allow",
                            "Principal": {
                                "Service": "lambda.amazonaws.com"
                            },
                            "Action": "sts:AssumeRole"
                        }
                    ]
                },
                "Policies": [
                    {
                        "PolicyName": "CognitoConfig",
                        "PolicyDocument": {
                            "Version": "2012-10-17",
                            "Statement": [
                                {
                                    "Sid": "Logging",
                                    "Effect": "Allow",
                                    "Action": [
                                        "logs:CreateLogGroup",
                                        "logs:CreateLogStream",
                                        "logs:PutLogEvents"
                                    ],
                                    "Resource": "*"
                                },
                                {
                                    "Sid": "Cognito",
                                    "Effect": "Allow",
                                    "Action": [
                                        "cognito-idp:CreateUserPool",
                                        "cognito-idp:DeleteUserPool",
                                        "cognito-idp:CreateUserPoolClient",
                                        "cognito-idp:DeleteUserPoolClient"
                                    ],
                                    "Resource": "*"
                                },
                                {
                                    "Sid": "ConfigBucketWriteAccess",
                                    "Effect": "Allow",
                                    "Action": [
                                        "s3:PutObject",
                                        "s3:PutObjectAcl",
                                        "s3:PutObjectVersionAcl"
                                    ],
                                    "Resource": [
                                        {
                                            "Fn::Sub": "arn:aws:s3:::${WebsiteBucket}/*"
                                        }
                                    ]
                                }
                            ]
                        }
                    }
                ]
            }
        },
        "UpdateConfigFunction": {
            "Properties": {
                "Description": "Copies objects from a source S3 bucket to a destination",
                "Handler": "index.handler",
                "Runtime": "python2.7",
                "Role": {
                    "Fn::GetAtt": [
                        "CognitoConfigRole",
                        "Arn"
                    ]
                },
                "Timeout": 120,
                "Code": {
                    "ZipFile": "import json\nimport boto3\nimport cfnresponse\n\ns3 = boto3.resource('s3')\n\ndef create(properties, physical_id):\n  userPoolId = properties['UserPool']\n  clientId = properties['Client']\n  region = properties['Region']\n  bucket = properties['Bucket']\n\n  object = s3.Object(bucket, 'js/config.js')\n  config_content = \"\"\"\nvar _config = {\n    cognito: {\n        userPoolId: '%s', // e.g. us-east-2_uXboG5pAb\n        userPoolClientId: '%s', // e.g. 25ddkmj4v6hfsfvruhpfi7n4hv\n        region: '%s', // e.g. us-east-2\n    },\n    api: {\n        invokeUrl: 'Base URL of your API including the stage', // e.g. https://rc7nyt4tql.execute-api.us-west-2.amazonaws.com/prod'\n    }\n};\n    \"\"\"\n  config_content = config_content % (userPoolId, clientId, region)\n  config = s3.Object(bucket,'js/config.js')\n  config.put(Body=config_content)\n  return cfnresponse.SUCCESS, None\n\ndef update(properties, physical_id):\n  return create(properties, physical_id)\n\ndef delete(properties, physical_id):\n  return cfnresponse.SUCCESS, physical_id\n\ndef handler(event, context):\n  print \"Received event: %s\" % json.dumps(event)\n\n  status = cfnresponse.FAILED\n  new_physical_id = None\n\n  try:\n    properties = event.get('ResourceProperties')\n    physical_id = event.get('PhysicalResourceId')\n\n    status, new_physical_id = {\n      'Create': create,\n      'Update': update,\n      'Delete': delete\n    }.get(event['RequestType'], lambda x, y: (cfnresponse.FAILED, None))(properties, physical_id)\n  except Exception as e:\n    print \"Exception: %s\" % e\n    status = cfnresponse.FAILED\n  finally:\n    cfnresponse.send(event, context, status, {}, new_physical_id)\n"
                }
            },
            "Type": "AWS::Lambda::Function"
        }
    }
}

実行したところ失敗した。

UpdateConfig
CREATE_FAILED	Failed to create resource. See the details in CloudWatch Log Stream: 2018/11/29/[$LATEST]33b8db152ea446ad8183e23523916a2f

こちら LambdaFunction の実行でエラーが出たために失敗となっている。 UpdateConfig を読むと、UpdateConfigFunction を呼びだしており、内容は S3 に展開済みの js/config.js の読み込みで失敗していると思われる。

ワークショップ

ドキュメントの各モジュールを手動で作成した。

モジュール 3 の作成で、権限エラーがでた。

– AWS Lambda および Amazon DynamoDBによるサーバーレスバックエンド https://aws.amazon.com/jp/getting-started/serverless-web-app/module-3/

DynamoDB への書き込みを作成したテーブルのみに制限する指定が期待した動作をしなかった。 対応として、全テーブルへのアクセス権を付与した。

ユーザープールにソーシャルサインインを追加する

こちらを参照した。

ドキュメント内容に従って Facebook デベロッパーを登録し、アプリを作成してIDを取得した。

ユーザプール側にIDプロバイダとクライアントアプリの登録を行った。

認証設定の「アプリクライアント設定」でコールバックURLがhttp のURLが不許可になっていた。 これに対応するため CloudFront で SSL Offloading を行う設定を追加した。

http://wildrydes-userpool-demo-02.s3-website-ap-northeast-1.amazonaws.com/index.html -> https://wildrydes-userpool-demo-02.s3.amazonaws.com/index.html

以下のURLでサードパーティ認証の画面が出るとの記述だったが、エラーしか得られていない。

https://c9575q4j0r92rr0j.auth.ap-northeast-1.amazoncognito.com/login?response_type=code&client_id=7rubtdlc3c0n2fmv6i0f714335&redirect_uri=https://wildrydes-userpool-demo-02.s3.amazonaws.com/ride.htmlー

前の 2 つのセクションの要素を使用してログイン URL を作成できます。この URL を使用してソーシャルIdP の設定をテストします。 https://your_user_pool_domain/login? response_type=code&client_id=your_client_id&redirect_uri=https://www.example.com ドメインは、コンソールにあるユーザープールの [ドメイン名] ページで確認できます。クライアント IDは [アプリクライアントの設定] ページにあります。redirect_uri パラメータのコールバック URL を使用します。コールバック URL アドレスは、ユーザープールのドメインとは異なります。

検証: AWS Amplify による UserPool ログイン

こちらを使用して Cognito UserPool と SNS Login の両対応を試みる。

各参考資料をもとに create-react-app の出力コードにCognito に設定済みの各IDを指定し、手を加えた。

現状のソースコードは以下のとおり。

import React, { Component } from 'react';
import Amplify from 'aws-amplify';
import { Authenticator } from 'aws-amplify-react';
import { withAuthenticator } from 'aws-amplify-react';

Amplify.configure({
  Auth: {
    identityPoolId: 'ap-northeast-1:736edc8f-4c51-4795-8548-4582105fd945', // FederatedID連携後に追加する
    region: 'ap-northeast-1', // REQUIRED - Amazon Cognito Region
    userPoolId: 'ap-northeast-1_wl3scNdwp', //OPTIONAL - Amazon Cognito User Pool ID
    userPoolWebClientId: '7rubtdlc3c0n2fmv6i0f714335', //OPTIONAL - Amazon Cognito Web Client ID
  }
});

const federated = {
    //google_client_id: '', // Enter your google_client_id here
    facebook_app_id: '216273885918169', // Enter your facebook_app_id here
    //amazon_client_id: '' // Enter your amazon_client_id here
};

class App extends Component {
}
export default withAuthenticator(App, true, <Authenticator federated={federated}/>, federated);

Facebook ログイン

こちらで Facebook ログインのアイコンが表示されたが、ログイン用のポップアップダイアログは表示されない。 ブラウザの Developper Tools では、以下のメッセージが出ている。

The resource at “https://connect.facebook.net/en_US/sdk.js” was blocked because content blocking is enabled.[Learn More] localhost:3000 Loading failed for the <script> with source “https://connect.facebook.net/en_US/sdk.js”. localhost:3000:1:1 TypeError: fb is undefined

解決していない。

Google ログイン

ダイアログの表示はできた。 正常なログインフォーム入力後のリダイレクトができていない。

検証 Facebook Login with Cognito using AWS Amplify

類似の実装を行っているコードを実行してみた。

こちらは Facebook ログインのダイアログは表示される。しかし、以下のメッセージが出てログインはできなかった。

Warning Can't Load URL: The domain of this URL isn't included in the app's domains. To be able to load this URL, add all domains and subdomains of your app to the App Domains field in your app settings.

Amazon Cognito の運用

AWS Well-Architected フレームワークにしたがって、本番環境とは別に開発とステージングは必要時に構築することを想定する。

    1. 現在の Cognito ユーザプールの設定と動作確認
    1. 作業用のユーザプールを CloudFormation テンプレートで設定し動作確認
    1. 手動でユーザプールの設定変更
    1. CloudFormation で変更の drift 確認
    1. drift を元にCloudFormation テンプレートを更新
    1. 更新した CloudFormation テンプレートで設定と動作確認
    1. 本番の Cognito ユーザプールに更新を適用

構成案

  • 大きく開発グループと,本番サービス運用グループを別アカウントに分ける
    • AWSサポートのプランを変えるため
  • 構成するクラウドは開発と、本番サービス & ステージングのあわせて 3 つ
    • ステージングは、本番サービスの CloudFormation のスタックをクローン、接続先データベース等をパラメータ入力で変更
  • 本番サービスでは IAM で Cognito UserPool の管理者と、クライアントアプリの管理者(ウェブマスター)を分ける
    • UserPool に利用者が登録する情報は個人情報なので。

注意点

  • drift を確認するには、登録している CloudFormation テンプレートに空値でも項目が必要。新しい追跡項目をテンプレートに書いて更新を行う。

本番サービス運用

本番サービスの運用に対して考慮する点。

検証: サーバレスと Cognito 連携

サーバーレスアプリケーション開発者用ツール https://aws.amazon.com/jp/serverless/developer-tools/

  • Chalice
  • Zappa

Chalice で作成した REST API と Cognito UserPool の連携

% chalice deploy
Creating deployment package.
Creating IAM role: aws-cognito-chalice-dev
Creating lambda function: aws-cognito-chalice-dev
Creating Rest API
Resources deployed:
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:410408491513:function:aws-cognito-chalice-dev
  - Rest API URL: https://7rb15x8ite.execute-api.ap-northeast-1.amazonaws.com/api/

chalice generate-pipeline

CloudFormation template 出力。

CodeBuild, CodePipeline, S3, IAM を使っている。 https://aws.amazon.com/jp/codebuild/

% chalice generate-pipeline "`basename $PWD`"_"`date +%Y%m%d-%H%M%S`".json
% ls -la *.json
-rw-r--r--  1 nicdk  staff  11954 Dec 10 15:56 aws-cognito-chalice_20181210-155646.json
% cat aws-cognito-chalice_20181210-155646.json
{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Parameters": {
    "ApplicationName": {
      "Default": "aws-cognito-chalice",
      "Type": "String",
      "Description": "Enter the name of your application"
    },
    "CodeBuildImage": {
      "Default": "aws/codebuild/python:3.6.5",
      "Type": "String",
      "Description": "Name of codebuild image to use."
    }
  },
  "Resources": {
    "SourceRepository": {
      "Type": "AWS::CodeCommit::Repository",
      "Properties": {
        "RepositoryName": {
          "Ref": "ApplicationName"
        },
        "RepositoryDescription": {
          "Fn::Sub": "Source code for ${ApplicationName}"
        }
      }
    },
    "ApplicationBucket": {
      "Type": "AWS::S3::Bucket"
    },
    "CodeBuildRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": [
                "sts:AssumeRole"
              ],
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "codebuild.amazonaws.com"
                ]
              }
            }
          ]
        }
      }
    },
    "CodeBuildPolicy": {
      "Type": "AWS::IAM::Policy",
      "Properties": {
        "PolicyName": "CodeBuildPolicy",
        "PolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
              ],
              "Resource": "*",
              "Effect": "Allow"
            },
            {
              "Action": [
                "s3:GetObject",
                "s3:GetObjectVersion",
                "s3:PutObject"
              ],
              "Resource": "arn:aws:s3:::*",
              "Effect": "Allow"
            }
          ]
        },
        "Roles": [
          {
            "Ref": "CodeBuildRole"
          }
        ]
      }
    },
    "AppPackageBuild": {
      "Type": "AWS::CodeBuild::Project",
      "Properties": {
        "Artifacts": {
          "Type": "CODEPIPELINE"
        },
        "Environment": {
          "ComputeType": "BUILD_GENERAL1_SMALL",
          "Image": {
            "Ref": "CodeBuildImage"
          },
          "Type": "LINUX_CONTAINER",
          "EnvironmentVariables": [
            {
              "Name": "APP_S3_BUCKET",
              "Value": {
                "Ref": "ApplicationBucket"
              }
            }
          ]
        },
        "Name": {
          "Fn::Sub": "${ApplicationName}Build"
        },
        "ServiceRole": {
          "Fn::GetAtt": "CodeBuildRole.Arn"
        },
        "Source": {
          "Type": "CODEPIPELINE",
          "BuildSpec": "version: 0.1\nphases:\n  install:\n    commands:\n      - sudo pip install --upgrade awscli\n      - aws --version\n      - sudo pip install 'chalice>=1.6.0,<1.7.0'\n      - sudo pip install -r requirements.txt\n      - chalice package /tmp/packaged\n      - aws cloudformation package --template-file /tmp/packaged/sam.json --s3-bucket ${APP_S3_BUCKET} --output-template-file transformed.yaml\nartifacts:\n  type: zip\n  files:\n    - transformed.yaml\n"
        }
      }
    },
    "AppPipeline": {
      "Type": "AWS::CodePipeline::Pipeline",
      "Properties": {
        "Name": {
          "Fn::Sub": "${ApplicationName}Pipeline"
        },
        "ArtifactStore": {
          "Type": "S3",
          "Location": {
            "Ref": "ArtifactBucketStore"
          }
        },
        "RoleArn": {
          "Fn::GetAtt": "CodePipelineRole.Arn"
        },
        "Stages": [
          {
            "Name": "Source",
            "Actions": [
              {
                "ActionTypeId": {
                  "Category": "Source",
                  "Owner": "AWS",
                  "Version": 1,
                  "Provider": "CodeCommit"
                },
                "Configuration": {
                  "BranchName": "master",
                  "RepositoryName": {
                    "Fn::GetAtt": "SourceRepository.Name"
                  }
                },
                "OutputArtifacts": [
                  {
                    "Name": "SourceRepo"
                  }
                ],
                "RunOrder": 1,
                "Name": "Source"
              }
            ]
          },
          {
            "Name": "Build",
            "Actions": [
              {
                "InputArtifacts": [
                  {
                    "Name": "SourceRepo"
                  }
                ],
                "Name": "CodeBuild",
                "ActionTypeId": {
                  "Category": "Build",
                  "Owner": "AWS",
                  "Version": 1,
                  "Provider": "CodeBuild"
                },
                "OutputArtifacts": [
                  {
                    "Name": "CompiledCFNTemplate"
                  }
                ],
                "Configuration": {
                  "ProjectName": {
                    "Ref": "AppPackageBuild"
                  }
                },
                "RunOrder": 1
              }
            ]
          },
          {
            "Name": "Beta",
            "Actions": [
              {
                "ActionTypeId": {
                  "Category": "Deploy",
                  "Owner": "AWS",
                  "Version": 1,
                  "Provider": "CloudFormation"
                },
                "InputArtifacts": [
                  {
                    "Name": "CompiledCFNTemplate"
                  }
                ],
                "Name": "CreateBetaChangeSet",
                "Configuration": {
                  "ActionMode": "CHANGE_SET_REPLACE",
                  "ChangeSetName": {
                    "Fn::Sub": "${ApplicationName}ChangeSet"
                  },
                  "RoleArn": {
                    "Fn::GetAtt": "CFNDeployRole.Arn"
                  },
                  "Capabilities": "CAPABILITY_IAM",
                  "StackName": {
                    "Fn::Sub": "${ApplicationName}BetaStack"
                  },
                  "TemplatePath": "CompiledCFNTemplate::transformed.yaml"
                },
                "RunOrder": 1
              },
              {
                "RunOrder": 2,
                "ActionTypeId": {
                  "Category": "Deploy",
                  "Owner": "AWS",
                  "Version": 1,
                  "Provider": "CloudFormation"
                },
                "Configuration": {
                  "StackName": {
                    "Fn::Sub": "${ApplicationName}BetaStack"
                  },
                  "ActionMode": "CHANGE_SET_EXECUTE",
                  "ChangeSetName": {
                    "Fn::Sub": "${ApplicationName}ChangeSet"
                  },
                  "OutputFileName": "StackOutputs.json"
                },
                "Name": "ExecuteChangeSet",
                "OutputArtifacts": [
                  {
                    "Name": "AppDeploymentValues"
                  }
                ]
              }
            ]
          }
        ]
      }
    },
    "ArtifactBucketStore": {
      "Type": "AWS::S3::Bucket",
      "Properties": {
        "VersioningConfiguration": {
          "Status": "Enabled"
        }
      }
    },
    "CodePipelineRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Policies": [
          {
            "PolicyName": "DefaultPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Action": [
                    "s3:GetObject",
                    "s3:GetObjectVersion",
                    "s3:GetBucketVersioning",
                    "s3:CreateBucket",
                    "s3:PutObject",
                    "s3:PutBucketVersioning"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
                },
                {
                  "Action": [
                    "codecommit:CancelUploadArchive",
                    "codecommit:GetBranch",
                    "codecommit:GetCommit",
                    "codecommit:GetUploadArchiveStatus",
                    "codecommit:UploadArchive"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
                },
                {
                  "Action": [
                    "cloudwatch:*",
                    "iam:PassRole"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
                },
                {
                  "Action": [
                    "lambda:InvokeFunction",
                    "lambda:ListFunctions"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
                },
                {
                  "Action": [
                    "cloudformation:CreateStack",
                    "cloudformation:DeleteStack",
                    "cloudformation:DescribeStacks",
                    "cloudformation:UpdateStack",
                    "cloudformation:CreateChangeSet",
                    "cloudformation:DeleteChangeSet",
                    "cloudformation:DescribeChangeSet",
                    "cloudformation:ExecuteChangeSet",
                    "cloudformation:SetStackPolicy",
                    "cloudformation:ValidateTemplate",
                    "iam:PassRole"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
                },
                {
                  "Action": [
                    "codebuild:BatchGetBuilds",
                    "codebuild:StartBuild"
                  ],
                  "Resource": "*",
                  "Effect": "Allow"
                }
              ]
            }
          }
        ],
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": [
                "sts:AssumeRole"
              ],
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "codepipeline.amazonaws.com"
                ]
              }
            }
          ]
        }
      }
    },
    "CFNDeployRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "Policies": [
          {
            "PolicyName": "DeployAccess",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Action": "*",
                  "Resource": "*",
                  "Effect": "Allow"
                }
              ]
            }
          }
        ],
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Action": [
                "sts:AssumeRole"
              ],
              "Effect": "Allow",
              "Principal": {
                "Service": [
                  "cloudformation.amazonaws.com"
                ]
              }
            }
          ]
        }
      }
    }
  },
  "Outputs": {
    "SourceRepoURL": {
      "Value": {
        "Fn::GetAtt": "SourceRepository.CloneUrlHttp"
      }
    },
    "S3ApplicationBucket": {
      "Value": {
        "Ref": "ApplicationBucket"
      }
    },
    "CodeBuildRoleArn": {
      "Value": {
        "Fn::GetAtt": "CodeBuildRole.Arn"
      }
    },
    "S3PipelineBucket": {
      "Value": {
        "Ref": "ArtifactBucketStore"
      }
    },
    "CodePipelineRoleArn": {
      "Value": {
        "Fn::GetAtt": "CodePipelineRole.Arn"
      }
    },
    "CFNDeployRoleArn": {
      "Value": {
        "Fn::GetAtt": "CFNDeployRole.Arn"
      }
    }
  }
}

(12/10時点) 使用している AWSアカウントでは、CodeBuild と CodePipeline の利用権限を付与していないので、動作確認できていない。 CloudTrailの履歴には「十分な AWS Config 権限がありません。」とのメッセージがでている。

IAM 管理権限を付与して再度実行。

chalice deploy
Creating deployment package.

Could not install dependencies:
pyyaml==3.13
You will have to build these yourself and vendor them in
the chalice vendor folder.

Your deployment will continue but may not work correctly
if missing dependencies are not present. For more information:
http://chalice.readthedocs.io/en/latest/topics/packaging.html

Updating policy for IAM role: aws-cognito-chalice-dev
Updating lambda function: aws-cognito-chalice-dev
Traceback (most recent call last):
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/awsclient.py", line 283, in _update_function_code
    FunctionName=function_name, ZipFile=zip_contents)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/botocore/client.py", line 320, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/botocore/client.py", line 624, in _make_api_call
    raise error_class(parsed_response, operation_name)
botocore.exceptions.ClientError: An error occurred (RequestEntityTooLargeException) when calling the UpdateFunctionCode operation: Request must be smaller than 69905067 bytes for the UpdateFunctionCode operation

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/deploy/deployer.py", line 330, in deploy
    return self._deploy(config, chalice_stage_name)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/deploy/deployer.py", line 343, in _deploy
    self._executor.execute(plan)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/deploy/executor.py", line 31, in execute
    self._default_handler)(instruction)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/deploy/executor.py", line 43, in _do_apicall
    result = method(**final_kwargs)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/awsclient.py", line 263, in update_function
    zip_contents=zip_contents)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/awsclient.py", line 290, in _update_function_code
    raise self._get_lambda_code_deployment_error(e, context)
chalice.awsclient.DeploymentPackageTooLargeError: An error occurred (RequestEntityTooLargeException) when calling the UpdateFunctionCode operation: Request must be smaller than 69905067 bytes for the UpdateFunctionCode operation

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/cli/__init__.py", line 447, in main
    return cli(obj={})
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/click/core.py", line 722, in __call__
    return self.main(*args, **kwargs)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/click/core.py", line 697, in main
    rv = self.invoke(ctx)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/click/core.py", line 1066, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/click/core.py", line 895, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/click/core.py", line 535, in invoke
    return callback(*args, **kwargs)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/click/decorators.py", line 17, in new_func
    return f(get_current_context(), *args, **kwargs)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/cli/__init__.py", line 183, in deploy
    deployed_values = d.deploy(config, chalice_stage_name=stage)
  File "/Users/nicdk/.pyenv/versions/3.6.0/lib/python3.6/site-packages/chalice/deploy/deployer.py", line 332, in deploy
    raise ChaliceDeploymentError(e)
chalice.deploy.deployer.ChaliceDeploymentError: ERROR - While sending your chalice handler code to Lambda to update function
"aws-cognito-chalice-dev", received the following error:

 An error occurred (RequestEntityTooLargeException) when calling the
 UpdateFunctionCode operation: Request must be smaller than 69905067 bytes for
 the UpdateFunctionCode operation

This is likely because the deployment package is 106.1 MB. Lambda only allows
deployment packages that are 50.0 MB or less in size. To avoid this error,
decrease the size of your chalice application by removing code or removing
dependencies from your chalice application.

エラーはでているが作成されている。

API Gateway に作成された helloworld より、ダッシュボードで URL を確認。

  • URL https://7rb15x8ite.execute-api.ap-northeast-1.amazonaws.com/api/

cognito 連携

Zappa で作成した REST API と Cognito UserPool の連携

実行。

%  zappa init

███████╗ █████╗ ██████╗ ██████╗  █████╗
╚══███╔╝██╔══██╗██╔══██╗██╔══██╗██╔══██╗
  ███╔╝ ███████║██████╔╝██████╔╝███████║
 ███╔╝  ██╔══██║██╔═══╝ ██╔═══╝ ██╔══██║
███████╗██║  ██║██║     ██║     ██║  ██║
╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝     ╚═╝  ╚═╝

Welcome to Zappa!

Zappa is a system for running server-less Python web applications on AWS Lambda and AWS API Gateway.
This `init` command will help you create and configure your new Zappa deployment.
Let's get started!

Your Zappa configuration can support multiple production stages, like 'dev', 'staging', and 'production'.
What do you want to call this environment (default 'dev'):

AWS Lambda and API Gateway are only available in certain regions. Let's check to make sure you have a profile set up in one that will work.
We found the following profiles: default, and bd. Which would you like us to use? (default 'default'):

Your Zappa deployments will need to be uploaded to a private S3 bucket.
If you don't have a bucket yet, we'll create one for you too.
What do you want to call your bucket? (default 'zappa-4qmujxmm7'):

What's the modular path to your app's function?
This will likely be something like 'your_module.app'.
Where is your app's function?:
Where is your app's function?: hello.app

You can optionally deploy to all available regions in order to provide fast global service.
If you are using Zappa for the first time, you probably don't want to do this!
Would you like to deploy this application globally? (default 'n') [y/n/(p)rimary]:

Okay, here's your zappa_settings.json:

{
    "dev": {
        "app_function": "hello.app",
        "aws_region": "ap-northeast-1",
        "profile_name": "default",
        "project_name": "aws-cognito-zap",
        "runtime": "python3.6",
        "s3_bucket": "zappa-4qmujxmm7"
    }
}

Does this look okay? (default 'y') [y/n]: y

Done! Now you can deploy your Zappa application by executing:

	$ zappa deploy dev

After that, you can update your application code with:

	$ zappa update dev

To learn more, check out our project page on GitHub here: https://github.com/Miserlou/Zappa
and stop by our Slack channel here: https://slack.zappa.io

Enjoy!,
 ~ Team Zappa!
% zappa deploy dev
Calling deploy for stage dev..
Creating aws-cognito-zap-dev-ZappaLambdaExecutionRole IAM Role..
Error: Failed to manage IAM roles!
You may lack the necessary AWS permissions to automatically manage a Zappa execution role.
To fix this, see here: https://github.com/Miserlou/Zappa#custom-aws-iam-roles-and-policies-for-deployment

実行失敗。IAM設定権限不足。

Serverless Framework で作成した REST API と Cognito UserPoolId

QuickStart だけで動作する。 (コマンドは短縮別名のslsを使用)

% sls create --template
Serverless: Generating boilerplate...

  Serverless Error ---------------------------------------

  Template "true" is not supported. Supported templates are: "aws-clojure-gradle", "aws-clojurescript-gradle", "aws-nodejs", "aws-nodejs-typescript", "aws-alexa-typescript", "aws-nodejs-ecma-script", "aws-python", "aws-python3", "aws-groovy-gradle", "aws-java-maven", "aws-java-gradle", "aws-kotlin-jvm-maven", "aws-kotlin-jvm-gradle", "aws-kotlin-nodejs-gradle", "aws-scala-sbt", "aws-csharp", "aws-fsharp", "aws-go", "aws-go-dep", "aws-go-mod", "aws-ruby", "azure-nodejs", "cloudflare-workers", "cloudflare-workers-enterprise", "fn-nodejs", "fn-go", "google-nodejs", "kubeless-python", "kubeless-nodejs", "openwhisk-java-maven", "openwhisk-nodejs", "openwhisk-php", "openwhisk-python", "openwhisk-ruby", "openwhisk-swift", "spotinst-nodejs", "spotinst-python", "spotinst-ruby", "spotinst-java8", "plugin" and "hello-world".

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com

  Your Environment Information -----------------------------
     OS:                     darwin
     Node Version:           10.7.0
     Serverless Version:     1.34.1

テンプレート aws-nodejs を使用して作成。

% sls create --template aws-nodejs
Serverless: Generating boilerplate...
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.34.1
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"
Serverless: NOTE: Please update the "service" property in serverless.yml with your service name

配備されるプログラム。

% cat handler.js
'use strict';

module.exports.hello = async (event, context) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: event,
    }),
  };

  // Use this code if you don't use the http event with the LAMBDA-PROXY integration
  // return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};

AWSに配備。 リージョンは初期値だと us-east-1 になる。

% sls deploy -v
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
CloudFormation - CREATE_IN_PROGRESS - AWS::CloudFormation::Stack - aws-nodejs-dev
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_IN_PROGRESS - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_COMPLETE - AWS::S3::Bucket - ServerlessDeploymentBucket
CloudFormation - CREATE_COMPLETE - AWS::CloudFormation::Stack - aws-nodejs-dev
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (608 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
CloudFormation - UPDATE_IN_PROGRESS - AWS::CloudFormation::Stack - aws-nodejs-dev
CloudFormation - CREATE_IN_PROGRESS - AWS::Logs::LogGroup - HelloLogGroup
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::Logs::LogGroup - HelloLogGroup
CloudFormation - CREATE_COMPLETE - AWS::Logs::LogGroup - HelloLogGroup
CloudFormation - CREATE_IN_PROGRESS - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_COMPLETE - AWS::IAM::Role - IamRoleLambdaExecution
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Function - HelloLambdaFunction
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Function - HelloLambdaFunction
CloudFormation - CREATE_COMPLETE - AWS::Lambda::Function - HelloLambdaFunction
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Version - HelloLambdaVersioncKAkuSOBXp8SGsMAYNd3d283HOANAFU08sRBTM9ins
CloudFormation - CREATE_IN_PROGRESS - AWS::Lambda::Version - HelloLambdaVersioncKAkuSOBXp8SGsMAYNd3d283HOANAFU08sRBTM9ins
CloudFormation - CREATE_COMPLETE - AWS::Lambda::Version - HelloLambdaVersioncKAkuSOBXp8SGsMAYNd3d283HOANAFU08sRBTM9ins
CloudFormation - UPDATE_COMPLETE_CLEANUP_IN_PROGRESS - AWS::CloudFormation::Stack - aws-nodejs-dev
CloudFormation - UPDATE_COMPLETE - AWS::CloudFormation::Stack - aws-nodejs-dev
Serverless: Stack update finished...
Service Information
service: aws-nodejs
stage: dev
region: us-east-1
stack: aws-nodejs-dev
api keys:
  None
endpoints:
  None
functions:
  hello: aws-nodejs-dev-hello
layers:
  None

Stack Outputs
HelloLambdaFunctionQualifiedArn: arn:aws:lambda:us-east-1:410408491513:function:aws-nodejs-dev-hello:1
ServerlessDeploymentBucketName: aws-nodejs-dev-serverlessdeploymentbucket-1ofy7squo5qfp

配備したAPIの応答を確認。

% sls invoke -f hello -l
{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"
}
--------------------------------------------------------------------
START RequestId: d66c373e-fd2a-11e8-8943-9bf3efef67d8 Version: $LATEST
END RequestId: d66c373e-fd2a-11e8-8943-9bf3efef67d8
REPORT RequestId: d66c373e-fd2a-11e8-8943-9bf3efef67d8	Duration: 3.29 ms	Billed Duration: 100 ms 	Memory Size: 1024 MB	Max Memory Used: 45 MB

更新向けのプログラムのみの配備。

% sls deploy function -f hello
Serverless: Packaging function: hello...
Serverless: Excluding development dependencies...
Serverless: Code not changed. Skipping function deployment.
Serverless: Successfully updated function: hello

配備したプログラムを削除。

% sls remove
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
.........
Serverless: Stack removal finished...

ユーザプールは消えない。

ローカルでの応答確認。

sls invoke local -f hello -l
{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":\"\"}"
}

Serverless Framework の注意点

1024MBのメモリで100ms実行すると、$0.000001667。

メモリが8倍になると、料金も約8倍になっている。

...

因みにserverless.ymlのメモリサイズの設定例は以下の通り。

provider:
  name: aws
  runtime: nodejs6.10
  memorySize: 128 # Overwrite the default memory size. Default is 1024

IAM Policy の作成

Well-Architected Framework のベストプラクティスに従って、ユーザではなくグループを作成してポリシーを割り当て、ユーザはグループに所属させる形で使用していた。 しかし、グループのポリシーのアタッチ数は10個までなので、限界がある。

そこで、必要なものを組み合わせたポリシーを作成し、それをグループに割り当てする。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "iam:*",
                "s3:*",
                "apigateway:*",
                "logs:*",
                "lambda:*",
                "cloudformation:*",
                "events:*"
            ],
            "Resource": "*"
        }
    ]
}

元記事では JSON エディタを使っていたが、ビジュアルエディタで作成した。

DynamoDB table 作成

DynamoDB テーブルを resource で指定。

% cat serverless.yml
service: aws-nodejs

provider:
  name: aws
  runtime: nodejs8.10
  memorySize: 128
  region: ap-northeast-1

# (function ブロック省略)

# you can add CloudFormation resource templates here
resources:
  Resources:
    usersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: usersTable
        AttributeDefinitions:
          - AttributeName: email
            AttributeType: S
        KeySchema:
          - AttributeName: email
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1

DynamoDB を配備。

% sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (625 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
.......................
Serverless: Stack update finished...
Service Information
service: aws-nodejs
stage: dev
region: ap-northeast-1
stack: aws-nodejs-dev
api keys:
  None
endpoints:
  None
functions:
  hello: aws-nodejs-dev-hello
  user: aws-nodejs-dev-user
layers:
  None

ユーザ認証 api

Auth0 に登録。

配備。

sls deploy

 Serverless Warning --------------------------------------

  A valid file to satisfy the declaration 'file(./public_key)' could not be found.

Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (127.04 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............................................................................
Serverless: Stack update finished...
Service Information
service: aws-custom-authorizer-auth0
stage: dev
region: ap-northeast-1
stack: aws-custom-authorizer-auth0-dev
api keys:
  None
endpoints:
  POST - https://qu163to86a.execute-api.ap-northeast-1.amazonaws.com/dev/api/public
  POST - https://qu163to86a.execute-api.ap-northeast-1.amazonaws.com/dev/api/private
functions:
  auth: aws-custom-authorizer-auth0-dev-auth
  publicEndpoint: aws-custom-authorizer-auth0-dev-publicEndpoint
  privateEndpoint: aws-custom-authorizer-auth0-dev-privateEndpoint
layers:
  None

endpoint を frontend/app.js に反映。

[EOF]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment