ブラウザ(JavaScript)からのAmazon S3へのアップロードの環境をCloudFormationでサクッと作ってみた。

福岡オフィスの梶原です。

AWSのドキュメントで公開されているJavascriptによるサンプル

ブラウザから Amazon S3 への写真のアップロード

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/s3-example-photo-album.html

を参考に、作成対象のAWSのリソースをCloudFormation化したので公開します。

※注意 Cognitoの未認可で割り当てられるRoleについては、アクセス元のIPアドレスだけで制限をかけていますがテンプレートのデフォルトのパラメータ0.0.0.0/0 では 世界中のすべてのユーザーに、バケット、およびバケット内のすべてのオブジェクトへの書き込みアクセス許可が付与されますので、制限がかからない状態になりますので、アクセス元のIPアドレスを設定するか、 実際に使用する場合はCognitoの認証を用いて、認証されているユーザに対しては書込み、認証されていないユーザは読み込みのみなどの権限を適切に割り当ててください。

構成図

環境構築

CloudFormationの実行

  • 作成するバケット(Website用)
  • アップロード先のバケット名
  • IP制限用のIPアドレス

を入力して、CloudFormationのテンプレートを実行してください。

IAMロールを作成するため

□AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。

にチェックを入れて実行します。

S3バケット(Webサイト)、S3バケット(アップロード)、Cognito ID プール, IAMRoleが作成されます。

HTML, Javascriptの配置

index.hmtl, javascriptを取得します。 (githubに上げてますのでご参照ください)

git clone https://github.com/ambasad/javascript-s3-upload-cfn.git
cd javascript-s3-upload-cfn/website/

Javascriptの編集

app.jsの

  • アップロード先のバケット名
  • Cognito ID プールを変更します。

IdentityPoolIdは、CloudFormationの出力またCognitoのコンソールから確認できます。

https://ap-northeast-1.console.aws.amazon.com/cognito/federated?region=ap-northeast-1

var albumBucketName = 'js-album-upload-bucket'; <== バケット名
var IdentityPoolId = 'ap-northeast-1:XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX';

HTML, Javascriptの配置

S3BucketWebsite=js-album-website <== (CloudFormationのパラメータで指定したWebsite用のバケット名)

# S3 へHTMLとjavascriptを配置します(--dryrun)
aws s3 sync . s3://$S3BucketWebsite --acl public-read --dryrun
(dryrun) upload: ./app.js to s3://js-album-website/app.js
(dryrun) upload: ./index.html to s3://js-album-website/index.html

# S3 へHTMLとjavascriptを配置します
aws s3 sync . s3://$S3BucketWebsite --acl public-read
upload: ./index.html to s3://js-album-website/index.html
upload: ./app.js to s3://js-album-website/app.js

配置後

http://バケット名.s3-website-ap-northeast-1.amazonaws.com

にアクセスしてみてください。特にエラーが表示されず

の画面が正常に表示されていれば、javasciptは正常に動作しており 各ボタンを押下することにより、

  • Create New Album (S3バケットへフォルダ作成)
  • Add Photo (S3バケットへファイルを保存)
  • X(フォルダ or ファイルを削除)

等が実施されます。 可能であれば、アクセス元のIPアドレスを変更して同じ動作をしてみてください。SDKの実行時にAccess Deniedになるかと思います。 (読み取りはpublicReadにより可能ですのでアップロードする画像についてはご注意ください) プログラムの内容については、参考元に詳細は記載されていますので、割愛させて頂きます。

https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/s3-example-photo-album.html

まとめ

コンソールでCognitoの認証周りの動作確認をするために、いくつか作っていたのですが 何度も環境を作るのが面倒だったのでCloudFormation化しました。次回はCognitoと連携させてみたいと思います。

参考

CloudFormation で Cognito

https://qiita.com/y13i/items/1923b47079bdf7c44eec

CloudFormation Template

AWSTemplateFormatVersion: "2010-09-09"
Parameters:
  S3BucketNameUpload:
    Type: String
    Default: js-album-upload-bucket
    Description: S3 BucketName for Upload
  S3BucketNameWebsite:
    Type: String
    Default: js-album-website
    Description: S3 BucketName for Website
  SourceIp:
    Type: String
    Default: 0.0.0.0/0
    Description: Allow S3 Bucket IpAddress ex. 10.0.48.0/32

Resources:
  # S3 Bucket (Upload)
  S3BucketUpload:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref S3BucketNameUpload
      AccessControl: PublicRead
      CorsConfiguration:
        CorsRules:
        - AllowedOrigins: ['*']
          AllowedMethods: [POST, GET, PUT, DELETE, HEAD]
          AllowedHeaders: ['*']

  # S3 Bucket (Website)
  S3BucketWebsite:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref S3BucketNameWebsite
      AccessControl: PublicRead
      WebsiteConfiguration:
        IndexDocument: "index.html"

  # IAM Role
  UnauthenticatedRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Federated: cognito-identity.amazonaws.com
          Action: sts:AssumeRoleWithWebIdentity
          Condition:
            StringEquals:
              cognito-identity.amazonaws.com:aud: !Ref CognitoIdentityPool
            ForAnyValue:StringLike:
              cognito-identity.amazonaws.com:amr: unauthenticated
      Policies: 
        - PolicyName: "Allow-S3-Policy"
          PolicyDocument: 
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - s3:*
                Resource:
                  - !Sub arn:aws:s3:::${S3BucketUpload}/*
                Condition:
                  IpAddress:
                    aws:SourceIp:
                      - !Ref SourceIp
        - PolicyName: "CognitoRole"
          PolicyDocument: 
            Version: "2012-10-17"
            Statement: 
              - Effect: "Allow"
                Action:
                  - mobileanalytics:PutEvents
                  - cognito-sync:*
                Resource: '*' 

  # Cognite ID pool
  CognitoIdentityPool:
    Type: AWS::Cognito::IdentityPool
    Properties:
      AllowUnauthenticatedIdentities: true

  CognitoIdentityPoolRoleAttachment:
    Type: AWS::Cognito::IdentityPoolRoleAttachment
    Properties:
      IdentityPoolId: !Ref CognitoIdentityPool
      Roles:
        unauthenticated: !GetAtt [UnauthenticatedRole, Arn]

Outputs:
  WebsiteURL:
    Value: !GetAtt [S3BucketWebsite, WebsiteURL]
    Description: URL for website hosted on S3
  CognitoIdentityPool:
    Value: !Ref CognitoIdentityPool
    Description: Cognito IdentityPool Id
  UploadBucketName:
    Value: !Ref S3BucketUpload
    Description: Upload BucketName
  WebsiteBucketName:
    Value: !Ref S3BucketWebsite
    Description: Website BucketName