Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

CDKアプリとしてのAmplify Gen2 - @aws-amplify/backendの...

CDKアプリとしてのAmplify Gen2 - @aws-amplify/backendのアーキテクチャにみるCDKベストプラクティス -

MURAKAMI Masahiko

July 06, 2024
Tweet

More Decks by MURAKAMI Masahiko

Other Decks in Technology

Transcript

  1. CDKアプリとしての Amplify Gen2 - @aws-amplify/backendのアーキテク チャにみる CDKベストプラクティス - AWS CDK

    Conference Japan 2024 presented by JAWS-UG 2024-07-06 株式会社永和システムマネジメント 村上雅彦 (a.k.a @fossamagna)
  2. 村上 雅彦 株式会社永和システムマネジメント Amplify Japan User Group 運営メンバー AWS Community

    Builder (Front-End Web & Mobile since 2022) GitHub: https://github.com/fossamagna X(旧Twitter): https://x.com/fossamagna 自己紹介
  3. ファイルベース規約 amplify ├── auth // 認証のカテゴリ │ └── resource.ts ├──

    data // GraphQL APIのカテゴリ │ └── resource.ts ├── backend.ts // バックエンド全体 ├── package.json └── tsconfig.json • Amplifyが提供する機能 毎のディレクトリ内の resource.tsに定義する • backend.tsにバックエン ド全体を定義する • どの機能のリソース定義 がどこにあるのか予測で きる
  4. TypeScriptによる 定義 // amplify/data/resource.ts const schema = a.schema({ Todo: a

    .model({ content: a.string(), }) .authorization((allow) => [allow.publicApiKey()]), }); export type Schema = ClientSchema<typeof schema>; 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が構築さ れる
  5. 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 してアプリのバックエド ン全体を構成する
  6. • 開発者毎の独立した sandbox環境を提供 • npx ampx sandboxコマン ド一発で環境構築完了 • hot

    swapデプロイ対応で 迅速にデプロイ可能 https://docs.amplify.aws/react/how-amplify-works/concepts/#faster-local-development sandbox環境
  7. • Gitブランチに対応するフルスタック環境 ◦ npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID

    • sandbox環境 ◦ npx ampx sandbox Amplify Gen2バックエンドのデプロイ
  8. • CDKアプリとして見ると Amplify Gen2 CLIプロセスは aws-cdkプロセスを呼び出す ラッパー • 赤枠部分はAWS CDKそのも

    の Amplify Gen2 バックエンドのプロセス構成 https://github.com/aws-amplify/amplify-backend/blob/main/PROJECT_ARCHITECTURE.md から引用
  9. • 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のベストプラクティスの参考資料
  10. • dataとauthの2つのL3 Construct ◦ @aws-amplify/data-construct ◦ @aws-amplify/auth-construct • @aws-amplify/<feature>-construct という規則のパッケージ名

    • 素早く簡単にAmplifyライクなリソースを 構築できる • Amplifyから独立しても使える L3 Constructsの作成 パッケージ構造の依存関係図: https://github.com/aws-amplify/amplify-backend/blob/main/PROJECT_ARCHITECTURE.md から引用
  11. @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', '<YOUR_USER_POOL_ID>'), }, }, }); • GraphQLのスキーマ定 義がTypeScriptではな いこと以外はほぼ Amplify Gen2と同じ • AppSync+DynamoDB というパターンに対して特 化した効果的なインタ フェース
  12. @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 ライ クな認証設定を簡単に設 定可能
  13. 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<T extends ResourceProvider = ResourceProvider> = { 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<T extends object = object> = { resources: T; }; • @aws-amplify/backen dでのCDKコンストラクトの 構築を担うクラス • synthプロセス内で呼び出 される • 機能間で依存するコンスト ラクトの解決(DIコンテナ) に利用される • 依存するコンストラクトの具 体的な構築方法を知らなく てよい
  14. 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 で ロールの付与を認証済みもしくはゲストユー ザーに制限する
  15. 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<string, Record<string, string>>; 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からの オペレーションに制限する指定がある か、ロールの付与を認証済みもしくは ゲストユーザーに制限する指定がある をチェック。
  16. 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に 登録される
  17. secrets export const handler = async ( event: CloudFormationCustomResourceEvent ):

    Promise<CloudFormationCustomResourceSuccessResponse > => { 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の場合、シークレッ ト値を取得して環境変数に設 定するランタイムコードが ユーザーコードの先頭に挿 入されデプロイされる