GitHub ActionsでPythonコードを実行してAWSリソースを操作する

こんにちは、カバー株式会社エンジニアのAです。

この記事は [カバー株式会社 Advent Calendar 2024](https://qiita.com/advent-calendar/2024/cover) 12日目の記事になります。
前回の記事は [@pvicugna11](https://qiita.com/pvicugna11) による [DocusaurusでWebページを作成してGithub Pagesで公開してみる！](https://qiita.com/pvicugna11/items/c2933f6d5a5e70ae3841) でした。こちらもぜひご覧ください。

# はじめに
この記事ではGitHub ActionsでPythonコードを実行してAWS上のリソースを操作するワークフローの実装についてご紹介します。

今回のワークフローを実装した経緯について、
- 任意のタイミングで一定の処理を手動実行したい
- 時間のかかる処理を行うのでLambdaのtimeout（15分）では足りない
- 少し複雑な処理を行うのでAWS CLIでは事足りない

という事情がありました。
GitHub Actionsは UIから手動実行可能（定期実行も可能）で、ログも残しやすく、新しく実行環境等を整備する必要も少ないため、これを利用しました。

# できたもの
```yaml:sample_workflow.yaml
name: Sample Workflow

on:
  workflow_dispatch: { }

jobs:
  SampleJob:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11.7'
          architecture: 'x64'

      - name: Install Dependencies
        run: |
          pip install pipenv
          pipenv install --dev

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.TF_VAR_AWS_ACCOUNT_ID }}:role/example_iam_role_name
          aws-region: ap-northeast-1

      - name: Run Python Script
        run: pipenv run python example.py
        env: 
          AWS_ROLE_ARN: arn:aws:iam::${{ secrets.TF_VAR_AWS_ACCOUNT_ID }}:role/example_iam_role_name
```

# 解説・ポイントなど

## OIDCによる認証
GitHubActionsワークフローのAWSに対する認証はOpenID Connect (OIDC)を利用しています。

https://docs.github.com/ja/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect

これを使うことで、GitHub Actionsで認証情報を保持する必要がなくなり、短期間有効なトークンを動的に取得することでセキュリティと運用効率を向上できます。

ワークフローでの認証の実行は以下の公式のActionsを利用しています。

https://github.com/aws-actions/configure-aws-credentials

`role-to-assume`にはGitHub Actionsに付与するロールを指定します。
以下は付与するロールのTerraformサンプルです。

```terraform:sample_role.tf
# OIDCで認証するために必要なAssumeRoleポリシー
data "aws_iam_policy_document" "sample_policy_document" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]
    principals {
      type        = "Federated"
      identifiers = ["arn:aws:iam::${var.aws_account_id}:oidc-provider/token.actions.githubusercontent.com"]
    }
    condition {
      test     = "StringEquals"
      variable = "token.actions.githubusercontent.com:aud"
      values   = ["sts.amazonaws.com"]
    }
    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values   = ["repo:OrganizationName/RepositoryName:ref:refs/heads/main"]
    }
  }
}

resource "aws_iam_role" "sample_role" {
  name               = "${var.prefix}-sample_role-${var.stage}"
  assume_role_policy = data.aws_iam_policy_document.sample_policy.json
}

# Pythonコードで扱うリソースへのアクセス許可ポリシー
resource "aws_iam_policy" "sample_policy" {
  name = "${var.prefix}-sample_policy-${var.stage}"
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Action" : [
          "s3:PutObject",
          "s3:GetObject",
          "s3:ListBucket",
          ...
        ],
        "Resource" : [
          "arn:aws:s3:::sample_arn",
        ]
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "sample_role" {
  role       = aws_iam_role.sample_role.name
  policy_arn = aws_iam_policy.sample_policy.arn
}

```


また、 OIDCでの認証を実行するには、ワークフローの`permission`で`id-token: write`を設定する必要があります（[参考](https://docs.github.com/ja/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services#adding-permissions-settings)）。
```
permissions:
    id-token: write
```

## Pythonコード（boto3）の実行
`example.py`内で、boto3を使った処理をいろいろ書いています。Pythonのパッケージ管理にはpipenvを利用しています。
実行時の環境変数に`AWS_ROLE_ARN: {使用するロールのARN}`を設定することで、boto3からAWSリソースにアクセスできます（[参考](https://zenn.dev/ano/articles/941128e17fa67d)）。
```
run: pipenv run python example.py
    env: 
        AWS_ROLE_ARN: arn:aws:iam::${{ secrets.TF_VAR_AWS_ACCOUNT_ID }}:role/example_iam_role_name
```
Pythonコード側で何か設定をする必要はありません。

# おわりに
今回の記事では、GitHub Actionsを活用してAWSリソースを操作するワークフローを構築する方法をご紹介しました。
手動実行やログ管理がしやすいGitHub Actionsは、小規模な運用から始めたいケースや既存の開発フローに簡単に組み込める点で有用です。今回はこれを利用することで、狭いケースに対して解決策を簡単に実装することができました。
何かのご参考になれば幸いです。