serverless frameworkでs3-persistence-adapterをまるっと設定する

# 概要
[交通量調査の達人](https://www.amazon.co.jp/dp/B07QZWTGQC/)というalexaスキルで、ユーザー毎の最高記録を保存する機能を追加しました。
今回はs3に保存する方法でやってみましたが、権限周りでハマったので参考までにまとめます。


## 前提
- serverless framerowkで各種リソースをまるっと管理します
    - S3 bucket
    - lambdaのIAM policy
    - lambdaのソースコード

## 結論
先に、正しく動いた時のコードを記載します。

```index.ts

// importとs3の設定
import * as Adapter from 'ask-sdk-s3-persistence-adapter';
const s3PersistenceAdapter = new Adapter.S3PersistenceAdapter({
     bucketName: `${process.env.SERVICE_NAME}-${process.env.ENV}`
 })

---

// s3を操作する関数
async function getScore(handlerInput: Alexa.HandlerInput): Promise<number> {
  const attributes = await handlerInput.attributesManager.getPersistentAttributes();
  if (Object.keys(attributes).length > 0) {
    return attributes.score;
  }
  return 0;
}

async function saveScore(handlerInput: Alexa.HandlerInput) {
  const sessionAttributes = handlerInput.attributesManager.getSessionAttributes() as QuestionAttributes;
  const persistentAttributes = {
    score: sessionAttributes.counter - 1
  };
  handlerInput.attributesManager.setPersistentAttributes(persistentAttributes);
  await handlerInput.attributesManager.savePersistentAttributes();
}

---
// メインのHandler
const AnswerHandler = {
  canHandle(handlerInput: Alexa.HandlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest'
      && request.intent.name === 'AnswerIntent';
  },
  async handle(handlerInput: Alexa.HandlerInput) {
      ...(省略)...

      //最高記録をs3から取得し、記録更新していたら更新する
      let score = await getScore(handlerInput)
      const isNewScore = score < attributes.counter
      if (isNewScore) {
        await saveScore(handlerInput);
      }

      ...(省略)...

      return handlerInput.responseBuilder
        .speak(gameOverText(attributes, isNewScore)) //gameOverText()は終了時のメッセージを組み立てる
        .withShouldEndSession(true)
        .getResponse();

}


```

```serverless.yml
service:
  name: sample-app

custom:
  defaultStage: dev

provider:
  name: aws
  region: ap-northeast-1
  runtime: nodejs8.10
  stage: ${opt:stage, self:custom.defaultStage}
  environment:
    ENV: ${opt:stage, self:custom.defaultStage}
    SERVICE_NAME: ${self:service.name}
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:*"
      Resource:
        - "arn:aws:s3:::${self:service.name}-${self:provider.stage}*"
resources:
  Resources:
    Bucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:service.name}-${self:provider.stage}

```

## NG集
s3を操作するための、lambdaに付与するIAM権限で結構はまりました。
serverless frameworkでは、iamRoleStatementsで記載できます。


### NG1: Resourceでbucket名のみ

```serverless.yml
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:*"
      Resource:
        - "arn:aws:s3:::${self:service.name}-${self:provider.stage}"

```

```error.log
Error handled: Could not save item (amzn1.ask.account.yyy) to bucket (ZZZ): Access Denied
```

bucketに対しての操作のみが許可されていて、objectに対する操作ができないためですね。これはわかる。


# NG2: 任意のobjectも許可
```serverless.yml
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:*"
      Resource:
        - "arn:aws:s3:::${self:service.name}-${self:provider.stage}/*"

```
```error.log
Error handled: Could not read item (amzn1.ask.account.yyy) from bucket (ZZZ): Access Denied
```
これは解せぬ...。
謎い。

# NG3: actionをgetとputのみに制限
```serverless.yml
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:GetObject"
        - "s3:PutObject"
      Resource:
        - "arn:aws:s3:::${self:service.name}-${self:provider.stage}*"

```
```error.log
Error handled: Could not read item (amzn1.ask.account.yyy) from bucket (ZZZ): Access Denied
```

これも解せぬ...。


### その他
[IAM Policy Simulator](https://policysim.aws.amazon.com/home/index.jsp#)で、IAM Policyを作成して、
GetとPutをしてみてると、大丈夫そうだしなー謎い。


```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::sample-bucket*"
            ],
            "Effect": "Allow"
        }
    ]
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328792/6634912d-4294-21cd-7870-7c6f8853bd68.png)