CI/CDパイプラインにおけるAWS ExpiredTokenExceptionの解決方法

intermediate☁️ AWS2026-07-11

エラーの発生CI/CDパイプラインが順調に動いていたのに、突然停止してしまうことがあります。すべてがストップし、ログには次のようなイライラさせるメッセージが表示されます。

An error occurred (ExpiredTokenException) when calling the ListBuckets operation: The security token included in the request is expired

これは通常、長時間のTerraform applyや、大容量のS3ファイルアップロードの最中に発生します。これは、生成した一時的なキーが有効期限に達したことを意味します。

なぜ発生するのかsts assume-roleを呼び出すと、AWSは一時的な認証情報セットを発行します。これらはIAM Userのシークレットキーのような恒久的なものではありません。これらはAccess Key ID、Secret Access Key、そして最終的に期限切れになるSession Tokenで構成されています。

デフォルトでは、これらのトークンは正確に**1時間(3600秒)**で期限切れになります。デプロイスクリプトに61分かかる場合、タスクの最後の10%が失敗することになります。また、現在のジョブのために新しい認証情報を生成するのではなく、古いビルドステップの環境変数をキャッシュしている場合にも、このタイムアウトが発生します。

ステップバイステップの解決策### 1. セッション時間を延長するロールを引き受ける(assume-role)際に、より長い有効期間をリクエストできます。AWSでは、セッションを最大12時間(43,200秒)まで持続させることが可能です。ただし、これを機能させるには2つの場所を更新する必要があります。

AWS CLIコマンドを更新する:

# 4時間のセッション(14400秒)をリクエストする
CREDENTIALS=$(aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/MyCiCdRole \
  --role-session-name "CiCdSession" \
  --duration-seconds 14400)

IAMロールを更新する: たとえ12時間を要求したとしても、ロール自体がそれ以上の時間を許可していない限り、AWSは1時間に制限します。**IAM > Roles > [ロール名]**に移動してください。Settingsタブで「Maximum CLI/API session duration」を見つけます。Editをクリックし、4時間以上に設定します。

2. Python (Boto3) で更新可能な認証情報を使用する標準のBoto3セッションは、一時的なトークンを自動的に更新しません。数千枚の画像を数時間かけて処理するPythonスクリプトがある場合、最初のトークンが期限切れになるとクラッシュします。これは、botocoreRefreshableCredentialsを使用することで解決できます。

import boto3
from botocore.credentials import RefreshableCredentials
from botocore.session import get_session

def get_refreshable_session():
    sts_client = boto3.client('sts')
    
    def refresh():
        # この関数はトークンの期限が切れる前に自動的に実行されます
        params = {
            "RoleArn": "arn:aws:iam::123456789012:role/MyRole",
            "RoleSessionName": "refreshable-session",
            "DurationSeconds": 3600
        }
        res = sts_client.assume_role(**params)
        creds = res['Credentials']
        return {
            "access_key": creds['AccessKeyId'],
            "secret_key": creds['SecretAccessKey'],
            "token": creds['SessionToken'],
            "expiry_time": creds['Expiration'].isoformat()
        }

    # advisory_refresh_timeout: 実際の期限の10分前に更新する
    session_creds = RefreshableCredentials.create_from_metadata_generator(
        metadata_generator=refresh,
        refresh_timeout=300,
        advisory_refresh_timeout=600
    )
    
    bc_session = get_session()
    bc_session._credentials = session_creds
    return boto3.Session(botocore_session=bc_session)

3. GitHub Actionsのワークフローを最新化するAWS_SESSION_TOKENをエクスポートするために、CLIからJSONを手動でパースするのはやめましょう。それは脆弱でメンテナンスが困難です。代わりに、公式のAWSアクションでOpenID Connect (OIDC) を使用してください。この方法では、トークンの交換を安全に処理し、期間も簡単に設定できます。

- name: Configure AWS Credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/MyRole
    aws-region: us-east-1
    role-duration-seconds: 7200 # 2時間のウィンドウ

セッションを確認する方法認証情報が有効かどうかを確認するには、aws sts get-caller-identityを実行します。ただし、これでは有効期限は表示されません。セッションが実際にいつ終了するかを確認するには、最初にassume-roleを実行したときのJSONレスポンスのExpirationフィールドを確認するか、現在のアクティブな設定を確認してください。

aws configure list

安定したパイプラインのためのプロのヒント

  • ロールの制限に注意: --duration-seconds 36000をリクエストしても、IAM Roleの設定が3600になっている場合、コマンドは即座にエラーで失敗します。
  • IAM Userキーを廃止する: GitHubやGitLabにはOIDCを使用しましょう。これにより、長期間有効なシークレットが不要になり、セッションウィンドウをよりスマートに管理できます。
  • 単に延長するだけでなく、最適化する: ジョブに3時間かかる場合は、ボトルネックがないか確認してください。時には、10時間のトークンを発行するよりも、10分間のコード最適化を行う方が効果的です。
  • セキュリティを維持する: これらのロールの信頼ポリシーを設定する際は、Password Generatorを使用して一意のExternal IDを作成し、混乱した代理(confused deputy)攻撃に対するセキュリティを強化してください。

Related Error Notes