こんにちは。SCSK石原です。
最近、お仕事ではないのですが不特定多数の人から画像を集める必要がありました。その時使用したナレッジがほかでも使いまわせそうでしたので、今回記事にさせていただきました。
[不特定多数の人が画像をアップロード出来るシステム] の個人的な要望としては
- 30分くらいで枠組みは作りたい
- 不特定多数なので認証不要でWebページにアクセスさせたい
- コンピュート層を持ちたくない(メンテナンスしたくない/お金をかけたくない)
- フロント側はすでにできているものを使いたい(HTMLやJavascriptが苦手)
上記の条件にぴったりな「Amazon S3バケットにフォトアルバムを作成するサンプル」を見つけましたので、こちらを利用させていただきました。ただし、AWS SDK for JavaScriptのサンプルになりますので、前提条件となるAWSサービスの詳細な説明がありませんでした。
本記事では、フォトアルバムを動作させる前提条件タスクとして定義されていることをCloudFormationテンプレートに変換し、ナレッジとして残しておくことが目的となります。
前提条件タスク
前述のサンプルには前提条件タスクというものがありました。
- Amazon S3 コンソールで、アルバムに写真を保存するために使用する Amazon S3 バケットを作成します。コンソールでのバケットの作成の詳細については、Amazon Simple Storage Service ユーザーガイドの「バケットの作成」を参照してください。オブジェクトで読み取りおよび書き込みの両方の許可があることを確認してください。バケットの許可の設定の詳細については、ウェブサイトアクセスに必要な許可の設定を参照してください。
- Amazon Cognito コンソールで、Amazon S3 バケットと同じリージョンの認証されていないユーザーに対してアクセスが有効になっているフェデレーテッドアイデンティティを使用して Amazon Cognito アイデンティティプールを作成します。コード内の ID プール ID を含めて、ブラウザスクリプトの認証情報を取得する必要があります。Amazon Cognito アイデンティティの詳細については、Amazon Cognito デベロッパーガイドの Amazon Cognito アイデンティティプール (フェデレーテッドアイデンティティ)を参照してください。
- IAM コンソールで、Amazon Cognito によって認証されていないユーザー用に作成された IAM ロールを見つけます。次のポリシーを追加し、Amazon S3 バケットに読み取りおよび書き込みの許可を付与します。IAM ロールの作成の詳細については、IAM ユーザーガイドのAWS のサービスに許可を委任するロールの作成を参照してください。
サンプルに記載のあるものを自身の言葉に落としたものが下記になります。
- Amazon S3 バケットを作成
- WebSiteホスティング用のS3バケットは公開設定でリソースを配置
- 画像アップロード用にはCORSを設定した別バケットを作成
- ユーザが認証なしで画像のアップロードバケットにアクセスできるようにCognitoのIDプールを設定
- アップロード用バケットにPutやDelete権限を保持したIAMロールを作成してCognitoに紐づけ
アーキテクチャのアイコンを並べると下記の様になります。
AWSマネジメントコンソールで作成してもよいのですが、再利用可能にするためテンプレートを作成して構成します。
前提条件タスク(テンプレート)
Cloudformationのテンプレートは下記の通りです。こちらのテンプレートでは、前述したフォトアルバムのサンプルを動作させるものになります。
AWSTemplateFormatVersion: "2010-09-09" Description: "SPA Template" Parameters: ProjectID: Type: String Resources: # ================================ # S3 # ================================ S3BucketWebsiteHosting: Type: "AWS::S3::Bucket" Properties: BucketName: !Sub "${ProjectID}-website" PublicAccessBlockConfiguration: BlockPublicAcls: False BlockPublicPolicy: False IgnorePublicAcls: False RestrictPublicBuckets: False BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 WebsiteConfiguration: ErrorDocument: "error.html" IndexDocument: "index.html" S3BucketPolicyWebsiteHosting: Type: "AWS::S3::BucketPolicy" Properties: Bucket: !Ref S3BucketWebsiteHosting PolicyDocument: Version: "2012-10-17" Statement: - Sid: "PublicReadGetObject" Action: - "s3:GetObject" Effect: Allow Resource: - "Fn::Join": - "" - - "arn:aws:s3:::" - Ref: S3BucketWebsiteHosting - /* Principal: "*" S3BucketUploadPicture: Type: "AWS::S3::Bucket" Properties: BucketName: !Sub "${ProjectID}-picture" PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 LifecycleConfiguration: Rules: - Id: IntelligentTierRule Status: Enabled Transitions: - TransitionInDays: 0 StorageClass: INTELLIGENT_TIERING CorsConfiguration: CorsRules: - AllowedHeaders: - '*' AllowedMethods: - GET - HEAD - PUT - POST - DELETE AllowedOrigins: - '*' ExposedHeaders: - ETag Id: myCORSRuleId1 # ================================ # Cognito # ================================ CognitoUnAuthIDPool: Type: AWS::Cognito::IdentityPool Properties: AllowUnauthenticatedIdentities: True IdentityPoolName: !Sub "${ProjectID}-cognitoidpool" RoleAttachment: Type: AWS::Cognito::IdentityPoolRoleAttachment Properties: IdentityPoolId: !Ref CognitoUnAuthIDPool Roles: unauthenticated: !GetAtt IAMRoleCognitoUnauth.Arn authenticated: !GetAtt IAMRoleCognitoUnauth.Arn # ================================ # IAM # ================================ IAMPolicyCognitoUnauth: Type: AWS::IAM::ManagedPolicy Properties: PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - mobileanalytics:PutEvents - cognito-sync:* Resource: - "*" - Effect: Allow Action: - s3:DeleteObject - s3:GetObject - s3:ListBucket - s3:PutObject - s3:PutObjectAcl Resource: - !Join - '' - - !GetAtt S3BucketUploadPicture.Arn - !Join - '' - - !GetAtt S3BucketUploadPicture.Arn - "/*" IAMRoleCognitoUnauth: 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: CognitoUnAuthIDPool ForAnyValue:StringLike: "cognito-identity.amazonaws.com:amr": unauthenticated ManagedPolicyArns: - !Ref IAMPolicyCognitoUnauth Outputs: WebSiteHostingURL: Description: WebSite URL Value: !GetAtt S3BucketWebsiteHosting.WebsiteURL CognitoIdPoolARN: Description: IdPool ARN Value: !Ref CognitoUnAuthIDPool
フォトアルバムを動かすには
GitHubにサンプルコードがありますので、WebSiteホスティング用のS3バケットに下記の2ファイルを少し修正して配置すれば出来上がりです。
- s3_photoExample.js
-
「BUCKET_NAME」をアップロード用のバケット名に置換
- 「REGION」をアップロード用のバケットが存在するリージョンに置換
- 「IDENTITY_POOL_ID」をCognitoのIDプールのIDに置換(CFNのアウトプットとして出力しています)
-
- s3_photoExample.html(ファイル名をindex.htmlに変更)
-
「SDK_VERSION_NUMBER」を利用するSDKのバージョンに置換
-
終わりに
これで30分ではなく、3分あればAWSにフォトアルバムをホスティングができるようになりました。
今回実施したS3ウェブサイトホスティングについても、もう少しお仕事向きなアーキテクチャにするのであれば、下記の様な構成が考えられます。
- CloudFrontの利用
- Cognitoにて認証を必須に
- Route53でドメイン取得&名前解決
- ACMで証明書取得&インストール
- CodeCommitでソース管理
- CodePipelineで自動デプロイ
以上、ぜひご活用ください!!