IAMロールとGitHub Actionsの統合によるデプロイのセキュリティ強化

これまで、AWS SDKをLambdaにデプロイする際のクレデンシャル(IAMユーザーのアクセスキーとシークレットアクセスキー)管理について課題を感じていました。

環境変数からリソースの強い操作権限をもったIAMユーザーのアクセスキーを読み込む方法だと、キーが流出したときのセキュリティリスクが大きいので、IAMロールを利用する方法に変更しました。

本記事では、この変更を実施する際の手順を備忘録としてまとめています。

# 手順
GitHub ActionsのデプロイワークフローでGitHub SecretsからAssumeRole権限をもったIAMユーザーのアクセスキーとシークレットアクセスキーを読み込み、AssumeRoleでIAMロールの権限を引き受けるようにしました。

## IAMユーザーの作成
コンソールやIaCツールからIAMユーザーを作成し、以下のカスタムポリシーを作成して関連付けます。
`Resource`欄については、IAMロール作成後にARNをコピペします。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ],
            "Resource": "arn:aws:iam::<Account ID>:role/<Role Name>"
        }
    ]
}
```
:::note
**追記**
`sts:AssumeRole`だけじゃなく、`sts:TagSession`も必須です。
:::


次に、「セキュリティ認証情報 > アクセスキー」からアクセスキー(およびシークレットアクセスキー)を発行します。

このアクセスキーは、GitHub ActionsのデプロイワークフローでAssumeRole(IAMロールの権限を引き受ける)際に必要となります。

:::note warn
**注意**
このIAMユーザーはリソースの操作権限を持っていません。
もしキーが第三者に漏れても、重大なリソース操作は行えないため、セキュリティのリスクは低いですが、それでも流出を防ぐための対策(キーのローテーションなど)が必要です。
:::

:::note
**追記 2024-01-07**
OpenID Connectを使用すれば、IAMユーザーの作成は不要です。

[OpenID Connectを使用してGitHub Actionsでアクセスキーレスなデプロイをおこなう](https://qiita.com/suzuki0430/items/c1fbde1550950ca41f7f)
:::

## IAMロールの作成
IAMユーザーと同様に、コンソールやIaCからIAMロールを作成します。

### デプロイワークフロー用のIAMロール
こちらのポリシーには、デプロイに必要なリソースの操作権限(CloudFormation, API Gateway, Lambda, ECR, IAMなど)を付与します。

次に、IAMロールの「信頼関係」タブに移動し、「信頼されたポリシー」の編集を選択します。信頼ポリシーでは、どのエンティティがロールを引き受けることができるかを定義します。

`Principal`の`AWS`には、先ほど作成したIAMユーザーのARNをコピペします。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::<Account ID>:user/<User Name>"
            },
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ],
            "Condition": {}
        }
    ]
}
```

### Lambda用のIAMロール
こちらのポリシーには、アプリケーションで必要なリソースの操作権限(S3, SESなど)を付与します。

Lambda詳細画面の「設定タブ > アクセス権限」から、実行ロールとして作成したIAMロールを関連付けます。
これでアプリケーションのコード上でクレデンシャルを読み込む必要がなくなります。
![スクリーンショット 2023-11-03 9.10.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/c5d2feb0-30f8-51cf-7154-86084e25c022.png)

## GitHub Secretsへの環境変数の登録
GitHubリポジトリのSettingsタブから「Secrets and variables > Actions」を選択し、「New repository secret」からワークフローで使用する環境変数(`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_IAM_ROLE_TO_ASSUME`)を登録します。

`AWS_IAM_ROLE_TO_ASSUME`にはIAMロールのARNを入力します。

![スクリーンショット 2023-11-03 8.58.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569054/25be152d-fbfa-a793-0170-17561ce45364.png)

## GitHub Actionsデプロイワークフローの修正
以下がデプロイワークフローの一部です。
`configure aws`のステップで先ほど設定した環境変数を読み込んでいます。

```yml
name: Lambda Deploy
on:
  push:
    branches:
      - develop
    paths:
      - "**"
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: install dependencies
        run: yarn install
        working-directory: ./

      - name: configure aws
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          role-to-assume: ${{ secrets.AWS_IAM_ROLE_TO_ASSUME }}
          role-duration-seconds: 3600
```

:::note warn
**注意**
`role-duration-seconds: 3600`の秒数はIAMロールの最大セッション時間以下に設定する必要があります。
:::

## ローカル環境での開発
Lambdaデプロイ後の環境ではLambdaの実行ロールでAWSリソースの操作が可能になりますが、ローカル環境(Dockerなど)では実行ロールがないため、従来通りクレデンシャルを環境変数から読み込む運用となります。

そのため、アプリケーションのコードを以下のように修正する必要があります。

```typescript
  private createS3Client(): S3Client {
    const options: S3ClientConfig = {
      region: 'ap-northeast-1',
      ...(process.env.ENV === 'local' && {
        credentials: {
          accessKeyId: process.env.ACCESS_KEY_ID as string,
          secretAccessKey: process.env.SECRET_ACCESS_KEY as string
        }
      })
    }

    return new S3Client(options)
  }
```

# おわりに
ECSにデプロイする場合も大体同じような手順です。
ECSタスク定義でタスクロールを指定することにより、ECSタスクが実行されるEC2インスタンスやFargate上で動作するコンテナがAWSリソースにアクセスできるようになります。
このタスクロールは、特定のAWSサービスやリソースに対するアクセス権限を定義するIAMポリシーをアタッチすることでカスタマイズ可能です。