🗑️

amplify-cliを使って複数のS3バケットを管理する

2022/04/12に公開

動機

プロジェクトで扱うデータを全ての1つのS3バケットにて管理しようとすると、例えば権限管理が複雑になりすぎてしまったり、またポリシーファイルに誤りがあった場合には、対象のバケットの全ての情報へのアクセスが許可されてしまう可能性がある。
他にも、 S3EventTrigger でアップロードされたファイルを処理してまたS3にアップロードしなおすような処理を記述した場合には、トリガーの発動条件次第では無限ループに陥ってしまう危険性がる。

そのようなケースにおいて、用途に応じて複数のバケットを管理し運用することは、より堅牢なサーバーレスアプリケーション開発のためには欠かせない要素である。

課題

現状の Amplify-CLI においてS3バケットを扱うには、 storage カテゴリとして追加する必要があるが、このS3バケットはプロジェクトごとに最大で1つのバケットしか登録することができず、amplify-cliの標準カテゴリを利用している限りではこの制約を超えることができない。

解決策

Amplify-CLI の提供する Custom Resource を利用して storage カテゴリで登録されるS3バケットとは別に新しいS3バケットを追加する

手順

1. Amplify-CLI の設定をする

amplify configure

表示されるプロンプトに応じてリージョンや IAM ユーザーのユーザー名、アクセス情報を設定する

2. 対象環境用に Amplify を構築する

対象のプロジェクトディレクトリに移動して

amplify init

を実行する。プロジェクト名を入力し、初期設定を行う。

3. storage で管理する1つめのS3バケットを追加する

amplify storage add

? Select from one of the below mentioned services?

に対して Content (Images, audio, video, etc.) と返すことでS3バケットを生成することができる。

4. custom で管理する2つめのS3バケットを追加する

amplify custom add

? How do you want to define this custom resource?

に対してはどちらを選んでも良いが、今回は AWS CDK を利用して進めることとする。

自動生成された amplify/backend/custom/<resourceName>/cdk-stack.ts に以下を記入する。

import * as cdk from '@aws-cdk/core';
import * as AmplifyHelpers from '@aws-amplify/cli-extensibility-helper';
import { AmplifyDependentResourcesAttributes } from '../../types/amplify-dependent-resources-ref';
import * as s3 from "@aws-cdk/aws-s3";
import * as iam from '@aws-cdk/aws-iam';

export class cdkStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps, amplifyResourceProps?: AmplifyHelpers.AmplifyResourceProps) {
    super(scope, id, props);
    /* Do not remove - Amplify CLI automatically injects the current deployment environment in this input parameter */
    new cdk.CfnParameter(this, 'env', {
      type: 'String',
      description: 'Current Amplify CLI env name',
    });
    const stackName = cdk.Fn.ref("AWS::StackName");
    const stackNameToken = cdk.Fn.split("-", stackName, 6);
    // stackに自動で付与されるランダム文字列を取得する
    const stackID = cdk.Fn.select(3, stackNameToken);
    const amplifyProjectInfo = AmplifyHelpers.getProjectInfo();
    // storageで作成されるバケットと命名規則を揃えておく
    const bucketNamePrefix = `${amplifyProjectInfo.projectName}-<bucketName>${stackID}`;
    const bucket = new s3.Bucket(this, "<bucketName>", {
      bucketName: `${bucketNamePrefix}-${cdk.Fn.ref('env')}`
    });
    // CORSの設定を行う
    bucket.addCorsRule({
      allowedHeaders: ["*"],
      allowedMethods: [
        s3.HttpMethods.GET,
        s3.HttpMethods.HEAD,
        s3.HttpMethods.PUT,
        s3.HttpMethods.POST,
        s3.HttpMethods.DELETE
      ],
      allowedOrigins: ["*"],
      exposedHeaders: [
        "x-amz-server-side-encryption",
        "x-amz-request-id",
        "x-amz-id-2",
        "ETag"
      ],
      maxAge: 3000
    })
    // ログイン済みユーザーへの書き込み権限を付与するために `auth` との依存関係を設定する
    const retVal: AmplifyDependentResourcesAttributes = AmplifyHelpers.addResourceDependency(this, 
      amplifyResourceProps.category, 
      amplifyResourceProps.resourceName, 
      [
        {category: "auth", resourceName: "<authResourceName>"}
      ]
    );
    // ログイン済みユーザーにアサインされるロールをArnから取得する
    const authRoleArn = cdk.Fn.join(":", [
        "arn",
        "aws",
        "iam",
        "",
        this.account,
        `role/amplify-${amplifyProjectInfo.projectName}-${cdk.Fn.ref('env')}-${stackID}-authRole`
    ]);
    const authRole = iam.Role.fromRoleArn(this, 'authRole', authRoleArn);
    // ログイン済みユーザーに読み書き権限を付与する
    bucket.grantReadWrite(authRole, 'private/${cognito-identity.amazonaws.com:sub}/*');
    // 別カテゴリから参照するために `BucketName` を `Output` に追加しておく
    new cdk.CfnOutput(this, 'BucketName', {
      value: bucket.bucketName,
      description: "The name of doclibrary bucket"
    });
  }
}

まとめ

上記の手順にしたがって構築を進めることで、 storagecustom カテゴリそれぞれに1つずつのS3バケットを作成することができる。 ログイン済みユーザーへの書き込み権限を付与する場合には storage カテゴリで構築するバケットでは対話インターフェース内で、 custom カテゴリで構築するバケットではCDKのソースコード内でそれぞれ設定することができるようになっている。

おまけ

なお、 storage で生成されるS3の設定をより細かく拡張したい場合には、

amplify storage override

を実行することで、生成後の CloudFormationTemplate に対して後処理を加えるためのコードを直接記述することができるようになる。

Discussion