amplify-cliを使って複数のS3バケットを管理する
動機
プロジェクトで扱うデータを全ての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
を実行する。プロジェクト名を入力し、初期設定を行う。
storage
で管理する1つめのS3バケットを追加する
3. amplify storage add
? Select from one of the below mentioned services?
に対して Content (Images, audio, video, etc.)
と返すことでS3バケットを生成することができる。
custom
で管理する2つめのS3バケットを追加する
4. 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"
});
}
}
まとめ
上記の手順にしたがって構築を進めることで、 storage
と custom
カテゴリそれぞれに1つずつのS3バケットを作成することができる。 ログイン済みユーザーへの書き込み権限を付与する場合には storage
カテゴリで構築するバケットでは対話インターフェース内で、 custom
カテゴリで構築するバケットではCDKのソースコード内でそれぞれ設定することができるようになっている。
おまけ
なお、 storage
で生成されるS3の設定をより細かく拡張したい場合には、
amplify storage override
を実行することで、生成後の CloudFormationTemplate に対して後処理を加えるためのコードを直接記述することができるようになる。
Discussion