[Serverless Framework]S3のファイルを読み取る

slack-slash-cm-and-serverless-005

はじめに

Serverless FrameworkでS3にアップロードしたファイルの内容を読み取るプログラムを書いていた所、Access Deniedエラーとなりました。文字通り権限の設定が不足していたのですが、どのタイミングでエラーが起きたのか、その解決方法などについて書いていきます。

サンプルプログラム

最初に今回のアプリの作成方法と、S3のファイルを読み取るプログラムを載せておきます。

以下のコマンドでServerless Frameworkを使ったプロジェクトを作成し、必要なモジュールをインストールします。

$ npm init
$ npm install serverless --save-dev
$ ./node_modules/.bin/serverless create --template aws-nodejs
$ npm install aws-sdk --save-dev
$ npm install byline --save-dev

serverless.ymlにS3のバケットと、イベント発生時に呼び出すLambda Functionを記述します。

serverless.yml
service: aws-nodejs # NOTE: update this with your service name

provider:
  name: aws
  runtime: nodejs4.3
  stage: dev # add
  region: ap-northeast-1 # add
(中略)
functions:
  hello:
    handler: handler.hello
    events:
      - s3: t-honda-serverless-s3-sample

Lambda Functionを記述したhandler.jsは以下の通りです。

handler.js
'use strict';

const AWS = require('aws-sdk');
const LineStream = require('byline').LineStream;

module.exports.hello = (event, context, callback) => {
  const bucket = event.Records[0].s3.bucket.name;
  const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
  const params = {
      Bucket: bucket,
      Key: key,
  };

  console.log(params);

  const s3 = new AWS.S3();
  var lineStream = new LineStream();
  var s3Stream = s3.getObject(params).createReadStream();
  s3Stream.setEncoding('utf-8');
  s3Stream
    .pipe(lineStream)
    .on('data', function(line) {
      console.log(line);
    });
};

この状態でデプロイします。

$ ./node_modules/.bin/serverless deploy

この状態でS3のバケットにファイルをアップロードすると、CloudWatchのログに以下のように出力されます。

(中略) { Bucket: 't-honda-serverless-s3-sample', Key: 'アップロードしたファイル名' }
(中略) { [AccessDenied: Access Denied] message: 'Access Denied', (中略) 

S3にアップロードしたファイル名やバケット名は取得できたようですが、18行目のS3からgetObject()を行う箇所でAccess Deniedとなったようです。

解決法

先にも書いたように、Access Deniedなので権限を設定すれば良いの筈です。serverless.ymlを良く読むと、デフォルトのコメントに以下の様な記述があります。

# you can add statements to the Lambda function's IAM Role here
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
#        - "s3:ListBucket"
#      Resource: (以降略)

このコメントに従い、以下の様な定義をserverless.ymlに追記しました。

serverless.yml
provider:
  name: aws
  runtime: nodejs4.3
  stage: dev # add
  region: ap-northeast-1 # add
  
  # (↓権限を追加した部分)
  iamRoleStatements: # add
    - Effect: Allow # add
      Action: # add
        - s3:* # add
      Resource: "arn:aws:s3:::t-honda-serverless-s3-sample/*" # add

この状態で再度S3にファイルをアップロードしたところ、CloudWatchのログは以下のようになりました。

(中略) { Bucket: 't-honda-serverless-s3-sample', Key: 'アップロードしたファイル名' }
(中略) ファイルの1行目
(中略) ファイルの2行目
・・・

まとめ

S3にアップロードしたファイル名の取得を行うだけでは特に権限の設定も必要なかったのですが、ファイルの内容を読み取るには権限が必要だったということでした。その権限を設定する箇所も、serverless.ymlのコメントとして記述されていました。少ない記述でS3のバケットの作成、イベント発生時のLambda Functionの呼び出しと権限の設定ができるのは、Serverless Frameworkの利点かと思います。