GitHub Actions ワークフローで AWS IAM の AssumeRole を実行するステップでタイムアウトを設定すると嬉しみがあるのか確認してみた

2024.06.25

こんにちは、製造ビジネステクノロジー部の若槻です。

GitHub Actionsでは、ワークフロー全体やジョブだけでなく、ステップにもタイムアウトを設定することができます。

今回は、GitHub Actions ワークフローで AWS IAM のAssumeRoleを実行するステップでタイムアウトを設定すると果たして嬉しみがあるのか確認してみました。

はじめに結論

AssumeRole を実行するステップでタイムアウトを設定することにより、以下のメリットおよびデメリットがあることが分かりました。

  • メリット
    • 失敗時のリトライによる不要な利用時間消費の削減
    • エラーの早期検出
  • デメリット
    • エラーメッセージが握りつぶされるため、デバッグが難しくなる

検証してみた

タイムアウトを設定しない場合

まずは、タイムアウトを設定しない場合の挙動を確認します。

成功パターン

AssumeRole が成功する場合はです。

以下の GitHub Actions との OIDC を許可するために以下の定義の OIDC Provider および IAM Role を AWS CDK により作成しておきます。

lib/cdk-sample-stack.ts

import { aws_iam, Stack, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CdkSampleStack extends Stack {
  private readonly GITHUB_OWNER = 'cm-rwakatsuki';
  private readonly GITHUB_REPO = 'cdk_sample_app';

  constructor(scope: Construct, id: string) {
    super(scope, id);

    const AWS_ACCOUNT_ID = this.account;

    new aws_iam.OpenIdConnectProvider(this, 'GithubActionsOidcProvider', {
      url: 'https://token.actions.githubusercontent.com',
      clientIds: ['sts.amazonaws.com'],
    });

    const gitHubActionsOidcRole = new aws_iam.Role(
      this,
      'GitHubActionsOidcRole',
      {
        assumedBy: new aws_iam.FederatedPrincipal(
          `arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com`,
          {
            StringEquals: {
              'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
            },
            StringLike: {
              'token.actions.githubusercontent.com:sub': `repo:${this.GITHUB_OWNER}/${this.GITHUB_REPO}:*`,
            },
          },
          'sts:AssumeRoleWithWebIdentity'
        ),
      }
    );

    new CfnOutput(this, 'GitHubActionsOidcRoleArnOutput', {
      value: gitHubActionsOidcRole.roleArn,
    });
  }
}

上記の IAM Role を使って AssumeRole を実行する GitHub Actions ワークフローを作成します。

.github/workflows/assume-role-permission-check.yml

name: Assume Role Permission Check
on: workflow_dispatch

jobs:
  PermissionCheck:
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Node.js 20
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Cache Dependency
        uses: actions/cache@v4
        id: cache_dependency
        env:
          cache-name: cache-dependency
        with:
          path: '**/node_modules'
          key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('package-lock.json') }}

      - name: Install Dependency
        if: ${{ steps.cache_dependency.outputs.cache-hit != 'true' }}
        run: npm ci --no-audit --progress=false --silent

      - name: Assume Role
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ap-northeast-1
          role-to-assume: ${{ vars.AWS_OIDC_ROLE_ARN }}

      - name: Permission Check
        run: aws sts get-caller-identity

ワークフローを実行すると、AssumeRole が成功しました。要した時間は 1 秒でした。

失敗パターン:不正な Role ARN が指定された場合

次に AssumeRole の失敗パターンとして、不正な Role ARN を指定した場合の挙動を確認します。ワークフローで指定している Role ARN を不正な値に変更します。

$ git diff                                                 
diff --git a/.github/workflows/assume-role-permission-check.yml b/.github/workflows/assume-role-permission-check.yml
index cc8c104..1a13d9d 100644
--- a/.github/workflows/assume-role-permission-check.yml
+++ b/.github/workflows/assume-role-permission-check.yml
@@ -34,7 +34,7 @@ jobs:
         uses: aws-actions/configure-aws-credentials@v4
         with:
           aws-region: ap-northeast-1
-          role-to-assume: ${{ vars.AWS_OIDC_ROLE_ARN }}
+          role-to-assume: INVALID_ROLE_ARN
 
       - name: Permission Check
         run: aws sts get-caller-identity

上記修正をしたワークフローを実行すると、AssumeRole 実行のステップで Source Account ID is needed if the Role Name is provided and not the Role Arn. というロール名が不正である旨のエラーメッセージが表示され、ステップが失敗しました。またステップの完了まで 2 分前後を要しました。

失敗パターン:IAM Role の権限が不足している場合

次に、AssumeRole の 2 つ目の失敗パターンとして、IAM Role の権限が不足している場合の挙動を確認します。IAM Role の権限を不足させるために、以下のようにコードを変更します。

$ git diff                                                 
diff --git a/lib/cdk-sample-stack.ts b/lib/cdk-sample-stack.ts
index 537dd33..0e69d26 100644
--- a/lib/cdk-sample-stack.ts
+++ b/lib/cdk-sample-stack.ts
@@ -26,7 +26,7 @@ export class CdkSampleStack extends Stack {
               'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
             },
             StringLike: {
-              'token.actions.githubusercontent.com:sub': `repo:${this.GITHUB_OWNER}/${this.GITHUB_REPO}:*`,
+              'token.actions.githubusercontent.com:sub': 'INVALID_SUB',
             },
           },
           'sts:AssumeRoleWithWebIdentity'

上記変更をデプロイした上でワークフローを実行すると、Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity という権限エラーのメッセージが表示され、ステップが失敗しました。また AssumeRole ステップの完了まで 1 分前後を要しました。

タイムアウトを設定した場合

続いて、AssumeRole ステップにタイムアウトを設定した場合の挙動を確認します。

成功パターン

まずは、AssumeRole が成功する場合です。ワークフローの AssumeRole ステップにタイムアウトを 1 分に設定します。

.github/workflows/assume-role-permission-check.yml

      - name: Assume Role
        timeout-minutes: 1
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ap-northeast-1
          role-to-assume: ${{ vars.AWS_OIDC_ROLE_ARN }}

ワークフローを実行すると、AssumeRole が成功しました。要した時間は 1 秒でした。エラーが発生しない場合は 1 分より明らかに短いのでタイムアウトの設定の有無は当然あまり関係してきませんね。

失敗パターン:不正な Role ARN が指定された場合

次に、不正な Role ARN を指定した場合の挙動を確認します。ワークフローで指定している Role ARN を不正な値に変更します。

$ git diff
diff --git a/.github/workflows/assume-role-permission-check.yml b/.github/workflows/assume-role-permission-check.yml
index 98c1023..a04174d 100644
--- a/.github/workflows/assume-role-permission-check.yml
+++ b/.github/workflows/assume-role-permission-check.yml
@@ -35,7 +35,7 @@ jobs:
         uses: aws-actions/configure-aws-credentials@v4
         with:
           aws-region: ap-northeast-1
-          role-to-assume: ${{ vars.AWS_OIDC_ROLE_ARN }}
+          role-to-assume: INVALID_ROLE_ARN
 
       - name: Permission Check
         run: aws sts get-caller-identity

すると 1 分間の実行の後に The action 'Assume Role' has timed out after 1 minutes.というエラーメッセージが表示され、ステップが失敗しました。その他に特にメッセージは表示されていません。

失敗パターン:IAM Role の権限が不足している場合

次に、IAM Role の権限が不足している場合の挙動を確認します。IAM Role の権限を不足させるために、以下のようにコードを変更します。

$ git diff
diff --git a/lib/cdk-sample-stack.ts b/lib/cdk-sample-stack.ts
index 84865c0..0e69d26 100644
--- a/lib/cdk-sample-stack.ts
+++ b/lib/cdk-sample-stack.ts
@@ -26,7 +26,7 @@ export class CdkSampleStack extends Stack {
               'token.actions.githubusercontent.com:aud': 'sts.amazonaws.com',
             },
             StringLike: {
-              'token.actions.githubusercontent.com:sub': `repo:${this.GITHUB_OWNER}/${this.GITHUB_REPO}:*`,
+              'token.actions.githubusercontent.com:sub': 'INVALID_SUB',
             },
           },
           'sts:AssumeRoleWithWebIdentity'

こちらもすると 1 分間の実行の後に The action 'Assume Role' has timed out after 1 minutes.というエラーメッセージが表示され、ステップが失敗しました。その他のメッセージとしては Assuming role with OIDC というログのみ表示されていました。

結論

ここまで検証してみて以下のような結論を得ることができました。

  • AssumeRole が成功した場合は 1 秒で完了するが、失敗した場合はアクションの内部的なリトライにより完了までに 1,2 分程度かかる。
  • AssumeRole のステップに 1 分のタイムアウトを設定することにより、リトライによる不要な利用時間消費を削減でき、またエラーの早期検出が可能となる。
  • しかし、エラーメッセージが握りつぶされるため、エラーの原因の特定が難しくなる場合がある。

メリットとデメリットを踏まえて、AssumeRole のステップにタイムアウトを設定するかどうかは、利用状況によって検討する必要がありそうです。

以上