Slide 1

Slide 1 text

CDKアプリとしての Amplify Gen2 - @aws-amplify/backendのアーキテク チャにみる CDKベストプラクティス - AWS CDK Conference Japan 2024 presented by JAWS-UG 2024-07-06 株式会社永和システムマネジメント 村上雅彦 (a.k.a @fossamagna)

Slide 2

Slide 2 text

村上 雅彦 株式会社永和システムマネジメント Amplify Japan User Group 運営メンバー AWS Community Builder (Front-End Web & Mobile since 2022) GitHub: https://github.com/fossamagna X(旧Twitter): https://x.com/fossamagna 自己紹介

Slide 3

Slide 3 text

● AWS CDKをベースとする次世代のAmplifyのバックエン ド構築ツール ● TypeScriptによるバックエンド定義を提供 ● CoC(設定より規約を優先する) ● GitのブランチとAWS環境を1:1でマッピング ● 開発者毎の独立したsandbox環境を提供 Amplify Gen2

Slide 4

Slide 4 text

● AWS CDKをベースとする次世代のAmplifyのバックエン ド構築ツール ● TypeScriptによるバックエンド定義を提供 ● ファイルベース規約(設定より規約を優先する) ● GitのブランチとAWS環境を1:1でマッピング ● 開発者毎の独立したsandbox環境を提供 Amplify Gen2の新機能

Slide 5

Slide 5 text

ファイルベース規約 amplify ├── auth // 認証のカテゴリ │ └── resource.ts ├── data // GraphQL APIのカテゴリ │ └── resource.ts ├── backend.ts // バックエンド全体 ├── package.json └── tsconfig.json ● Amplifyが提供する機能 毎のディレクトリ内の resource.tsに定義する ● backend.tsにバックエン ド全体を定義する ● どの機能のリソース定義 がどこにあるのか予測で きる

Slide 6

Slide 6 text

TypeScriptによる 定義 // amplify/data/resource.ts const schema = a.schema({ Todo: a .model({ content: a.string(), }) .authorization((allow) => [allow.publicApiKey()]), }); export type Schema = ClientSchema; export const data = defineData({ schema, authorizationModes: { defaultAuthorizationMode: "apiKey", // API Key is used for a.allow.public() rules apiKeyAuthorizationMode: { expiresInDays: 30, }, }, }); ● defineData関数でリ ソースを宣言的に定義 ● TypeScriptで GraphQLのSchemaと APIの認証設定を記述 するのみ ● この例ではAppSync, DynamoDBによる GraphQL APIが構築さ れる

Slide 7

Slide 7 text

TypeScriptによる 定義 // amplify/backend.ts import { defineBackend } from '@aws-amplify/backend' ; import { auth } from './auth/resource'; import { data } from './data/resource'; defineBackend({ auth, data, }); ● defineBackendでバッ クエンド全体を定義 ● 機能毎の定義をimport してアプリのバックエド ン全体を構成する

Slide 8

Slide 8 text

● prodcutionやstagingといった環境がGitのブランチと 1 : 1 で対応 ● Gitのブランチへpushすることで自動でデプロイされる https://docs.amplify.aws/react/how-amplify-works/concepts/#faster-local-development Gitベースのフルスタック環境

Slide 9

Slide 9 text

● 開発者毎の独立した sandbox環境を提供 ● npx ampx sandboxコマン ド一発で環境構築完了 ● hot swapデプロイ対応で 迅速にデプロイ可能 https://docs.amplify.aws/react/how-amplify-works/concepts/#faster-local-development sandbox環境

Slide 10

Slide 10 text

Amplify Gen2バックエンドの内側

Slide 11

Slide 11 text

● Gitブランチに対応するフルスタック環境 ○ npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID ● sandbox環境 ○ npx ampx sandbox Amplify Gen2バックエンドのデプロイ

Slide 12

Slide 12 text

● CDKアプリとして見ると Amplify Gen2 CLIプロセスは aws-cdkプロセスを呼び出す ラッパー ● 赤枠部分はAWS CDKそのも の Amplify Gen2 バックエンドのプロセス構成 https://github.com/aws-amplify/amplify-backend/blob/main/PROJECT_ARCHITECTURE.md から引用

Slide 13

Slide 13 text

● Best practices for developing and deploying cloud infrastructure with the AWS CDK (AWS CDKを使用したクラウドイン フラストラクチャの開発とデプロイのベストプラ クティス) ● Best practices for using the AWS CDK in TypeScript to create IaC projects - AWS Prescriptive Guidance (AWS 規範ガ イダンス AWS CDK で TypeScript を使用し て IaC プロジェクトを作成するためのベストプ ラクティス) AWS CDKのベストプラクティスの参考資料

Slide 14

Slide 14 text

L3 Constructの作成

Slide 15

Slide 15 text

● dataとauthの2つのL3 Construct ○ @aws-amplify/data-construct ○ @aws-amplify/auth-construct ● @aws-amplify/-construct という規則のパッケージ名 ● 素早く簡単にAmplifyライクなリソースを 構築できる ● Amplifyから独立しても使える L3 Constructsの作成 パッケージ構造の依存関係図: https://github.com/aws-amplify/amplify-backend/blob/main/PROJECT_ARCHITECTURE.md から引用

Slide 16

Slide 16 text

@aws-amplify/ data-construct // Cognito ユーザープールベースの認証を使用したシンプルな Todo リスト import { App, Stack } from 'aws-cdk-lib'; import { UserPool } from 'aws-cdk-lib/aws-cognito'; import { AmplifyData, AmplifyDataDefinition } from '@aws-amplify/data-construct'; const app = new App(); const stack = new Stack(app, 'TodoStack'); new AmplifyData(stack, 'TodoApp', { definition: AmplifyDataDefinition.fromString(/* GraphQL */ ` type Todo @model @auth(rules: [{ allow: owner }]) { description: String! completed: Boolean } `), authorizationModes: { userPoolConfig: { userPool: UserPool.fromUserPoolId(stack, 'ImportedUserPool', ''), }, }, }); ● GraphQLのスキーマ定 義がTypeScriptではな いこと以外はほぼ Amplify Gen2と同じ ● AppSync+DynamoDB というパターンに対して特 化した効果的なインタ フェース

Slide 17

Slide 17 text

@aws-amplify/ auth-construct // SMSを使ったMFA設定を使用したEメールログイン import { App, Stack } from "aws-cdk-lib"; import { AmplifyAuth } from "@aws-amplify/auth-construct"; const app = new App(); const stack = new Stack(app, "AuthStack"); new AmplifyAuth(stack, "Auth", { loginWith: { email: true, }, multifactor: { mode: "OPTIONAL", sms: { smsMessage: (code: string) => `Your verification code is ${code}`, }, totp: false, }, }); ● 設定オブジェクトによる宣 言的な設定 ● Amplify の auth が提 供するWebアプリ、モバ イル向けのAmplify ライ クな認証設定を簡単に設 定可能

Slide 18

Slide 18 text

Abstract Factoryパターン を利用する *万人向けではありません

Slide 19

Slide 19 text

ConstructFactory // amplify-backend/packages/plugin-types/src/construct_factory.ts /** * Functional interface for construct factories. All objects in the backend-engine definition must implement this interface. */ export type ConstructFactory = { readonly provides?: string; getInstance: (props: ConstructFactoryGetInstanceProps) => T; }; // amplify-backend/packages/plugin-types/src/resource_provider.ts /** * Provides reference to underlying CDK resources. */ export type ResourceProvider = { resources: T; }; ● @aws-amplify/backen dでのCDKコンストラクトの 構築を担うクラス ● synthプロセス内で呼び出 される ● 機能間で依存するコンスト ラクトの解決(DIコンテナ) に利用される ● 依存するコンストラクトの具 体的な構築方法を知らなく てよい

Slide 20

Slide 20 text

https://github.com/aws-amplify/amplify-backend/blob/main/PROJECT_ARCHITECTURE.md から引用 ConstructFactoryの実装 Amplify Gen2 バックエンドのプロセス構成の詳細

Slide 21

Slide 21 text

Aspectsを使ってセキュリティプ ロパティに関する アサーションを行う

Slide 22

Slide 22 text

Cognito IdPの 信頼ポリシー { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "cognito-identity.amazonaws.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "cognito-identity.amazonaws.com:aud": "us-west-2:abcdefg-1234-5678-910a-0e8443553f95" }, "ForAnyValue:StringLike": { "cognito-identity.amazonaws.com:amr": "authenticated" } } } ] } ● sts:AssumeRoleWithWebIden tity を許可する信頼ポリシーには 適切な条件が設定されている必要 がある ○ モバイルやWebアプリなどWebベースのIDプロ バイダーを使用して認証されたユーザーに、 IAM Roleの一時的なセキュリティ認証情報を 発行するAPIリクエスト ○ cognito-identity.amazonaws.com:aud で 特定のCognito IdentityPoolからのオペレー ションに制限する ○ cognito-identity.amazonaws.com:amr で ロールの付与を認証済みもしくはゲストユー ザーに制限する

Slide 23

Slide 23 text

Aspectsによるポリ シーの条件チェック // packages/backend/src/engine/amplify_stack.ts から一部抜粋 class CognitoRoleTrustPolicyValidator implements IAspect { visit = (node: IConstruct ) => { ... const assumeRolePolicyDocument = node.assumeRolePolicy ?.toJSON(); assumeRolePolicyDocument .Statement .forEach( this.cognitoTrustPolicyStatementValidator ); }; private cognitoTrustPolicyStatementValidator = ({ Action: action, Condition: condition , Effect: effect, Principal: principal , }: { // These property names come from the IAM policy document which we do not control Action: string; Condition ?: Record>; Effect: 'Allow' | 'Deny'; Principal ?: { Federated ?: string }; }) => { ... const audCondition = condition ?.StringEquals ?.['cognito-identity.amazonaws.com:aud' ]; if (typeof audCondition !== 'string' || audCondition .length === 0) { throw new AmplifyFault ('InvalidTrustPolicyFault' , { message: 'Cannot create a Role trust policy with Cognito that does not have a StringEquals condition for cognito-identity.amazonaws.com:aud' , }); } ... }; } ● Aspectsを使ってセキュリティに関する チェックを実施している ● CognitoRoleTrustPolicyValidatorと いうAspectでCognito Idpの sts:AssumeRoleWithWebIdentity のActionを付与するロールに条件が 含まれているのかを検証している ● 特定のCognito IdentityPoolからの オペレーションに制限する指定がある か、ロールの付与を認証済みもしくは ゲストユーザーに制限する指定がある をチェック。

Slide 24

Slide 24 text

カスタムリソース

Slide 25

Slide 25 text

secrets import { defineAuth, secret } from '@aws-amplify/backend' ; export const auth = defineAuth({ loginWith: { email: true, externalProviders: { facebook: { // fooという名前のシークレットを利用 clientId: secret('foo'), clientSecret: secret('bar') } } } }); ● Amplify Gen2でのシーク レット値を管理する機能 ● secret関数にシークレット の名前を指定するだけで参 照可能。 ● シークレット値はアプリ全 体、ブランチ毎にシークレッ ト値を設定できる ● SSM Parameter Storeに 登録される

Slide 26

Slide 26 text

secrets export const handler = async ( event: CloudFormationCustomResourceEvent ): Promise => { console.info(`Received ' ${event.RequestType }' event` ); const physicalId = event.RequestType === 'Create' ? randomUUID () : event.PhysicalResourceId ; let data: { secretValue : string } | undefined = undefined ; if (event.RequestType === 'Update' || event.RequestType === 'Create' ) { // SSM からシークレットを取得する const val = await handleCreateUpdateEvent (secretClient , event); data = { secretValue: val, }; } return { RequestId: event.RequestId , LogicalResourceId: event.LogicalResourceId , PhysicalResourceId: physicalId , Data: data, StackId: event.StackId, NoEcho: true, Status: 'SUCCESS' , } as CloudFormationCustomResourceSuccessResponse ; }; ● SSMから対象ブランチまたは アプリ全体のシークレット値 を取得する CustomResourceProvide rとして実装されている ● シークレットを参照する側が Lambdaの場合、シークレッ ト値を取得して環境変数に設 定するランタイムコードが ユーザーコードの先頭に挿 入されデプロイされる

Slide 27

Slide 27 text

● AWS::DynamoDB::Tableを置き換えるAmplify用DynamoDBTableカ スタムリソース ● 一度で複数のGSIの更新をサポート ○ isCompleteはDynamoDBへの変更処理行う。 WaiterStateMachineはisCompleteがエラーを返さなくまで(複数 のDynamoDBへの更新処理が完了するまで)retryする Custom::AmplifyDynamoDBTable

Slide 28

Slide 28 text

● Amplify Gen2 で使われているベストプラクティス、L3 ConstructやAbstract Factoryパターンにより利用する 側はより宣言的にリソースを定義できる。 ● AWS CDK をベースにしたプラットフォームを提供する場 合の参考になる。 まとめ

Slide 29

Slide 29 text

ご清聴ありがとうございました!