🔄

GitHub ActionsでAWS S3バケットへデプロイする

に公開

概要

GitHub Actionsで静的データをS3バケットへsyncさせるフローとAWS認証設定の方法です。
備忘録としてまとめます。

下記は予め準備できている前提です。

  • GitHubリポジトリ
  • AWSアカウント・S3バケット

01. AWSでIDプロバイダを作成する

サービスメニュー「IAM」から「IDプロバイダ」 > 「プロバイダを追加」ボタンをクリック。

IDプロバイダの画面で下記のように設定し、「プロバイダを追加」ボタンをクリック。

  • プロバイダのタイプ:OpenID Connect
  • プロバイダのURL:https://token.actions.githubusercontent.com
  • 対象者:sts.amazonaws.com

02. IAMロールの作成

IDプロバイダを作成すると追加したプロバイダが表示されます。
画面上部ポップアップの「ロールの割り当て」ボタンをクリック。
(または左サイドメニュー「アクセス管理 > ロール」からでもOK)

「新しいロールを作成」で次へ

信頼されたエンティティタイプ:ウェブアイデンティティまたはカスタム信頼ポリシーを選択
ロール作成時、「信頼ポリシー(JSON)」を設定しますが、ウェブアイデンティティだとGUIで設定した項目が自動的に「信頼ポリシー(JSON)」に反映されます。
カスタム信頼ポリシーは自身でJSONの内容を設定します。
どちらを選択しても作成後に「信頼ポリシー(JSON)」の編集は可能です。
キャプチャはウェブアイデンティティで進めた場合です。

  • アイデンティティプロバイダー:token.actions.githubusercontent.com
  • Audience:sts.amazonaws.com
  • GitHub organization:organization名
  • GitHub repository:リポジトリ名
  • GitHub branch:任意のブランチ名(main, stagingなど)

カスタム信頼ポリシーを選択した場合は、下記のように信頼ポリシーをJSONに記述します。
(もし下記のJSON設定で認証がうまくいかない場合はこちらを参照してください。)

JSONの詳細はこちら
  • Version:"2012-10-17"がポリシー言語の現行バージョン
  • Statement:ポリシーが許可・拒否する具体的なルール定義のブロック。1つ以上含む。
  • Effect:ActionをAllow(許可)かDeny(拒否)
  • Action:Allow(許可)かDeny(拒否)するアクション
  • Principal:誰が権限を与えるか
  • Federated:AWSの外部にある認証プロバイダ(IdP)を信頼する場合に指定する(今回指定した値は「GitHubのOIDCトークンを発行するIDプロバイダを信頼する」ことを示す)
  • Condition:特定の条件(ブランチ名やリポジトリなど)に限定するオプション
  • StringEquals:完全一致。指定文字列と「全く同じ」でなければマッチしない
  • StringLike:部分一致。ワイルドカード(*)などを使って柔軟に指定できる

■ Statementの中に複数ルールを定義する場合
Statementを区別するためにSidに任意の名称を指定することできますが、必須ではありません。

"Statement": [
  {
    "Sid": "GitHubActionsAccess",
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::<AWSアカウントID>:oidc-provider/token.actions.githubusercontent.com"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": { ... }
  },
  {
    "Sid": "LambdaAccess",
    "Effect": "Allow",
    "Principal": {
      "Service": "lambda.amazonaws.com"
    },
    "Action": "sts:AssumeRole"
  }
]

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Principal": {
        "Federated": "arn:aws:iam::<AWSアカウントID>:oidc-provider/token.actions.githubusercontent.com"
      },
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud":"sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub":"repo:<GitHubユーザー名>/<リポジトリ名>:ref:refs/heads/<ブランチ名(main, stagingなど)>"
        }
      }
    }
  ]
}

次は「許可ポリシー」です。

ロールにアタッチするポリシーを選択できますが後ほどマニュアルで作成するため、チェックを入れず「次へ」ボタンをクリック。

最後に任意の「ロール名」を入力して完了です。

03. 許可ポリシーの作成

ロールが作成後、先程後回しにした許可ポリシーをカスタムで作成します。
左サイドメニュー「アクセス管理 > ポリシー」から「ポリシーの作成」ボタンをクリック。

ポリシーエディタはJSONを選択。

エディタで下記を設定。

JSONの詳細はこちら
  • s3:PutObject:バケット内のファイルを追加または上書き
  • s3:DeleteObject:バケット内のファイルを削除
  • s3:ListBucket:バケット内のオブジェクト一覧を閲覧
  • Resource:どのAWS里シースに対してポリシーを適用するかを指定。ポリシーが適用される。"arn:aws:s3:::<S3バケット名>"はバケットそのもの、"arn:aws:s3:::<S3バケット名>/*"はバケット内のファイル(オブジェクト)を指す。
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:ListBucket"
	  ],
      "Resource": [
        "arn:aws:s3:::<S3バケット名>",
        "arn:aws:s3:::<S3バケット名>/*"
      ]
    }
  ]
}

記述できたら次へ進みます。

任意のポリシー名を入力し「ポリシーの作成」をクリック。

次に作成したポリシーを先程作ったロールにアタッチします。
左サイドメニュー「アクセス管理 > ロール」から作成したロールを検索し、許可ポリシーの「許可を追加」ボタンをクリック。「ポリシーをアタッチ」を選択します。

ポリシー名を検索してヒットしたらチェックボックスをクリックし、「許可を追加」ボタンをクリック。

これでロールにポリシーがアタッチできました。AWSでの設定はこれで完了です。

04. GitHubリポジトリでsecretsの登録

ワークフローを作成するにあたりAWS認証に必要な情報をリポジトリのSettings > Secrets and variables > Actions > Environment secretsに登録します。「Manage environment secrets」をクリックします。

最初に任意のNameを登録します。今回はmain, staging両方のEnvironmentを作成しますので、まずはstagingを入力して「Configure environment」をクリック。

これは必須でありませんが、Environment secretsの情報にアクセスできるブランチを制限したい場合は設定します↓
作成されたstagingのEnvironmentsで「Deployment branches and tags」をSelected branches and tagsにし「Add deployment branch or tag rule」をクリック。

typeをBranchstagingを入力して「Add rule」をクリック。

「Add environment secret」をクリックし、NameとValueを登録。

今回登録した情報は3つです。

  • AWS S3バケット名:機密情報というわけではありませんので登録必須ではありません。
  • AWS リージョン:上記同様、必須登録ではありません。
  • AWS ARN:IDも含まれるため必須登録です。(作成したIAMロールのARN)

入力後「Add secrets」ボタンをクリック。

最後に「Workflow permissions」でRead nad write permissionsが選択されていることを確認します。

05. GitHub Actionsワークフローの作成

リポジトリの静的データをS3バケットにデプロイするためのワークフローを設定します。

YAML内容の詳細はこちら
  • pushイベント(stagingまたはmainブランチへのpush)でワークフローが起動します
  • id-token: writeが重要です。これがないとGitHubがOIDCトークン(id_token)を発行できず、sts:AssumeRoleWithWebIdentityによるロール取得ができません
  • contents: readにしてactions/checkout等でリポジトリの中身を読むための権限を制限します
  • timeout-minutesは任意の時間を指定します
  • environmentでmainブランチの場合はproduction、そうでない場合はstagingのEnvironment secretsを参照するように分岐します
  • stepsのConfigure AWS credentialsでOIDCを使用しAWS認証を行っています。
    1. GitHubがid-token(OIDCトークン)を生成してAWSに渡す
    2. aws-actions/configure-aws-credentialsはそのOIDCトークンを使ってAWSのAssumeRoleWithWebIdentityを呼び、指定した role-to-assume(ARN)を引き受ける
    3. 成功すると一時的なAWSのアクセスキー(環境変数)をrunnerに設定し、その後のawsコマンドはその権限で動く
  • stepsのDeployでローカル環境の./dist/(アップロードしたいファイルがあるディレクトリを指定)の中身をS3バケット直下にsyncします
name: Deploy to AWS S3

on:
  push:
    branches:
      - staging
      - main

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    timeout-minutes: 30
    runs-on: ubuntu-latest

    environment: ${{github.ref == 'refs/heads/main' && 'production' || 'staging'}}

    steps:
      - name: Checkout
        uses: actions/checkout@v5
        with:
          persist-credentials: false

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v5
        with:
          role-to-assume: ${{secrets.AWS_ROLE_ARN}}
          aws-region: ${{secrets.AWS_REGION}}

      - name: Deploy
        run: |
          aws s3 sync ./dist/ s3://${{ secrets.AWS_BUCKET }} --delete

以上で一通りの設定が完了しました。認証がうまくいくか確認します。

認証がうまくいかない場合

S3へデプロイできるかを確認するためにプルリクエストをブランチへマージした際、GitHub ActionsのAWS認証段階で「Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity」というエラーメッセージが出て失敗しました。
理由としてはブランチにプッシュではなく、プルリクエストをマージしたからではないかと思います。

解決方法を検索し、下記のように信頼ポリシーを修正しましたがうまくいきませんでした。

修正前
"StringLike": {
  "token.actions.githubusercontent.com:sub": [
    "repo:<GitHubユーザー名>/<リポジトリ名>:ref:refs/heads/main"
  ]
}
修正後
"StringLike": {
  "token.actions.githubusercontent.com:sub": [
    "repo:<GitHubユーザー名>/<リポジトリ名>:ref:refs/heads/main",
    "repo:<GitHubユーザー名>/<リポジトリ名>:pull_request"
  ]
}

結局、*(ワイルドカード)指定にすることで認証がうまくいきました。

解決
"StringLike": {
  "token.actions.githubusercontent.com:sub": [
    "repo:<GitHubユーザー名>/<リポジトリ名>:ref:*
  ]
}

できればワイルドカードでなく特定のブランチに条件を絞りたかったですが、代わりにGitHub Actionsのワークフロー(ymlファイル)で特定のブランチでない場合は中断させるなどの方法も検討できそう?です。

参考

https://zenn.dev/kou_pg_0131/articles/gh-actions-oidc-aws
https://qiita.com/satooshi/items/0c2f5a0e2b64a1d9a4b3
https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws

Discussion