AWS CodeStarで作成したAlexa SkillをDynamoDBにつなげてみる

## はじめに

先日、個人開発で作った Alexa Skill をリリースしました！

https://www.amazon.co.jp/dp/B0C88Y5Q1F/

開発の中でAWS CodeStarによるAlexa Skillプロジェクトの作成を検討しました。
本記事は、その時のメモになります。

## AWS CodeStarを使ったAlexa Skill開発

### AWS CodeStarとは

AWS CodeStarは、ボイラープレートによるプロジェクトの立ち上げから、コードの構成管理、CI/CDも含めた開発環境までを簡単に構築してくれるツールです。
サービス名にCodeと付いていることもあり、AWSのCode3兄弟(CodeCommit, CodeDeploy, CodePipeline)を組み合わせてCI/CD環境に自動作成してくれるというのが売りです。

### AWS CodeStarによるAlexa Skillの作成

CodeStarのコンソール画面にて「プロジェクトの作成」をクリックすると、テンプレートでAlexa Skillのハローワールドスキルが表示されます。これを選択して、指示に従っていけばNode.jsベースのAlexa Skillプロジェクトを立ち上げられます。

- ソースコード管理はCodeCommitもGithubも選択可能
- Alexa Developer Consoleとの連携もGUI上で対応
- Alexa Developer Console上にhello nodeというスキルが作成される
- 作成と同時にCloudformationが走るのを確認。プロビジョニングにかなり時間がかかった。
- CodePipelineが作られていることも確認できる

### Tips

- 最初はAlexa Developer Console上でテストを実行できなかった。
=> スキルの状態を「開発中」に変更すれば、実行可能になった。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173520/edc10452-7660-1737-670c-d7849b9d7b76.png" width="70%">

- [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/codestar/latest/userguide/alexa-tutorial.html)より、CodeStarプロジェクトの「アプリケーションの表示」ボタンからAlexa Simulatorを起動できるらしい

> 1. AWS CodeStar コンソールのプロジェクトで、[アプリケーションを表示] を選択します。Alexa Simulator で新しいタブが開きます
> 2. ステップ 1 でプロジェクトに接続したアカウントの Amazon 開発者認証情報を使用してサインインします。
> 3. [Test] (テスト) の下で [Development] (開発) を選択してテストを有効にします。

=> 実際にはシミュレータは起動せず、スキルが実装されているLambdaが表示された

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/173520/c6272edf-00a4-641e-e8d1-fcbf1cb16a7a.png" width="90%">

いろいろ調べたが、Alexa Developer Consoleのシミュレータが表示する方法は分からなかった。

- hello nodeというスキル名は、skill.jsonとja-JP.jsonを編集すれば変更できた
- masterブランチへのマージをトリガーにして、デプロイが実行される

## DynamoDBとの接続

ローンチしたサービスでは、DynamoDBからデータを読み込む必要があったので、DynamoDBとの接続方法を試しました。
デプロイされるインフラ設定は、Cloudformationテンプレートとしてtemplate.ymlに記載されています。
ここに記載されているLambdaのIAMポリシーに、DynamoDBへのアクセスを許可する記述を追記。

```yaml
      ManagedPolicyArns:
        - !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      PermissionsBoundary: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:policy/CodeStar_${ProjectId}_PermissionsBoundary"
      Policies:
        - PolicyName: "DndbTableAccessPolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "dynamodb:Get*"
                  - "dynamodb:Query"
                  - "dynamodb:Scan"
                Resource:
                  - "Fn::Join":
                      - ":"
                      - - "arn:aws:dynamodb"
                        - Ref: AWS::Region
                        - Ref: AWS::AccountId
                        - "table/MyDndbTable-dev"
                  - "Fn::Join":
                      - ":"
                      - - "arn:aws:dynamodb"
                        - Ref: AWS::Region
                        - Ref: AWS::AccountId
                        - "table/MyDndbTable-dev/index/*"
```

しかし、これだけだとアクセスに失敗します。

```text
INFO	Failed to query events;  AccessDeniedException: User: arn:aws:sts::xxxxxxxx:assumed-role/CodeStar-alx-cdstar-test-Execution/awscodestar-alx-cdstar-test-lambda-CustomDefaultFunction is not authorized to perform: dynamodb:Query on resource: arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxx:table/MyDndbTable-dev/index/GSI1 because no permissions boundary allows the dynamodb:Query action
    at Request.extractError (/var/task/node_modules/aws-sdk/lib/protocol/json.js:52:27)
    at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
    at Request.emit (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
    at Request.emit (/var/task/node_modules/aws-sdk/lib/request.js:686:14)
    at Request.transition (/var/task/node_modules/aws-sdk/lib/request.js:22:10)
    at AcceptorStateMachine.runTo (/var/task/node_modules/aws-sdk/lib/state_machine.js:14:12)
    at /var/task/node_modules/aws-sdk/lib/state_machine.js:26:10
    at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:38:9)
    at Request.<anonymous> (/var/task/node_modules/aws-sdk/lib/request.js:688:12)
    at Request.callListeners (/var/task/node_modules/aws-sdk/lib/sequential_executor.js:116:18) {
  code: 'AccessDeniedException',
  time: 2023-01-30T13:16:51.933Z,
  requestId: 'KN8JIRSGDFKNVKJORPN5KM0T6FVV4KQNSO5AEMVJF66Q9ASUAAJG',
  statusCode: 400,
  retryable: false,
  retryDelay: 18.96747783566036
}
```

### Troubleshooting

原因はLambdaのIAMロールについていた~PermissionsBoundaryという記述。

Permissions Boundaryとはサービスに許可するアクセスに事前に範囲(ロールの境界)を指定できるIAMの機能です。つまり、サービスに紐づけるIAMロール自体に制限を設ける機能です。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/access_policies_boundaries.html

このPermissionsBoundaryの記述をtemplate.ymlからコメントアウトしてみましたが、その場合はデプロイができなくなりました。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Action": [
                "*"
            ],
            "Resource": [
                "arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/awscodestar-alx-cdstar-test-*",
                "arn:aws:codebuild:ap-northeast-1:xxxxxxxxxxxx:project/alx-cdstar-test",
                "arn:aws:codecommit:ap-northeast-1:xxxxxxxxxxxx:alexa-codestar-test",
                "arn:aws:codedeploy:ap-northeast-1:xxxxxxxxxxxx:application:alx-cdstar-test",
                "arn:aws:codedeploy:ap-northeast-1:xxxxxxxxxxxx:deploymentgroup:alx-cdstar-test/*",
                "arn:aws:codepipeline:ap-northeast-1:xxxxxxxxxxxx:alx-cdstar-test-Pipeline",
                "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:awscodestar-alx-cdstar-test-lambda-CustomDefaultFunction",
                "arn:aws:s3:::aws-codestar-ap-northeast-1-xxxxxxxxxxxx-alx-cdstar-test-pipe",
                "arn:aws:s3:::aws-codestar-ap-northeast-1-xxxxxxxxxxxx-alx-cdstar-test-pipe/*",
                "arn:aws:dynamodb:ap-northeast-1:xxxxxxxxxxxx:table/*" // <= 追記★
            ]
        },
```

そこで上記のように、IAMコンソールからPermissionsBoundaryとして指定されているポリシーにDynamoDBへのアクセスを許可する記述を追記すれば、アクセスできるようになります。

あと、こちらはケアレスミスですが、
最初、IAMポリシー上でのDynamoDBのリソースの記載を以下のように書いてました。

```yaml
Resource:
    - "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/MyDndbTable-dev"
    - "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/MyDndbTable-dev/index/*"
```

Serverless Frameworkの場合はこれで通る気がしたのですが、純粋なAWS Cloudformation templateではダメでした。

```text
The policy failed legacy parsing (Service: AmazonIdentityManagement; Status Code: 400; Error Code: MalformedPolicyDocument; Request ID: 0daa4c1a-e614-4550-8a61-696a02a6b73c; Proxy: null)
```

既に書いたように、Fn::JoinとRefの組み込み関数を使って書いて解決しました。

Alexa Hosted Skillとは異なり、自身のAWSアカウントでAlexa Skillをホスティングすることになるので、上記の設定でDynamoDBからデータを読み込めるようになります。

## まとめ

AWS CodeStarを使ってみましたが、小規模な開発で、AWS内でCI/CDを完結させたいなら、利用してみるのもいいかもしれません。

私は、Github Actionsを使いたかったことや、便利な反面、開発の自由度が下がってしまうこと、今回のように全ての設定をプロジェクト内のコード上に記述できない点などを危惧して、今回はCodeStarは使わずにサービスをローンチしました。

しかし、Alexa Skillプロジェクトには、skill.jsonやask-states.jsonといったスキルのステータスを保持するファイルがあり、これにAWSアカウントが記載されてしまいます。
パブリックなレポジトリの場合、AWSアカウントがさらされてしまうので、これらのファイルをGithubリポジトリに格納したくない方はいるでしょう。
その場合、CodeCommitの利用が選択肢に入ってきます。
そしてCodeCommitを使うなら、手軽にCodeCommit上のプロジェクトのセットアップができるCodeStarを検討してみるのがいいでしょう。