PythonバージョンアップでAssumeRoleが失敗!VPC Endpointの落とし穴
この記事は、ナウキャスト Advent Calendar 2025の3日目の記事です。
はじめに
こんにちは。ナウキャストでデータエンジニアをしている大城です。
本記事では、あるデータパイプラインで発生した「Pythonのバージョンを上げたらAssumeRoleができなくなった問題」の原因と解決策について解説します。
また、AWSで構築しているシステムの、特にネットワークと権限のトラブルシューティングの流れの一例を共有します。
AWSでシステムを構築・運用する方々の何かしらの参考になれば幸いです。
本記事における前提知識
以下の用語について基本的な知識と理解があることを前提としています。
- 権限管理: IAM Role/Policy, STS, AssumeRole
- ネットワーク: VPC, Subnet, NAT Gateway, VPC Endpoint
三行まとめ
- Python 3.9→3.11のアップグレードに伴いBoto3のバージョンも上がり、STSリクエスト時にデフォルトでグローバルエンドポイントではなくリージョナルエンドポイントを使うようになりました。また、VPC Endpointを設定していました。そのため、STSリクエストがNAT GatewayからVPC Endpoint経由になった結果、接続元IPが変わり、クロスアカウントのAssumeRoleがIP制限で弾かれるようになっていました。
- 短期の対応としては、グローバルエンドポイントを明示的に使用するようコードを修正しました。長期的には、クロスアカウント先と調整し、aws:SourceIpではなくaws:SourceVpceによる制限を行うようポリシー見直しを検討します。
- バージョンを上げるときはライブラリの変更点にも気を配りましょう。また、VPC EndpointとプライベートDNS名有効化時の挙動には注意しましょう。
背景・前提
ある日次バッチは、アカウントAのECSタスク(タスクロール:Role A)が社外アカウントBのRole BをAssumeRoleしてアカウントBのS3からアカウントAのS3にコピーする処理を実行しています。
ECSタスクはプライベートサブネットに配置され、0.0.0.0/0 がNAT Gatewayを向くようにサブネットのルートテーブルは設定されています。
アカウントB側のRole Bの信頼ポリシーでは、Role AからのAssumeRoleが許可されており、接続元IPがNAT GatewayのグローバルIPのときのみ許可するよう制限しています。
なお、アカウントAのVPCにはSTSのVPC Endpointが作成され、プライベートDNS名が有効になっています。
システム構成イメージ(※IPアドレスは例示です)

発生した事象
日次バッチのPythonを3.9から3.11にアップグレードしたところ、インフラ構成を変えていないにも関わらず、ECSタスクでAssumeRoleが失敗するようになりました。
エラーメッセージ
"errorMessage": "User: arn:aws:sts::111111111111:assumed-role/role-a/yyyyyyyy is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::222222222222
:role/role-b",
一方で、同一サブネットに配置している作業用EC2上のAWS CLIでは問題なくAssumeRoleが成功していました。
原因
原因はBoto3のデフォルト挙動の変更とVPC設定が重なったことでした。
- Boto3のバージョン1.40より、STSリクエストにデフォルトでリージョナルエンドポイントを使用するようになりました。
- アカウントAのVPCでは、STSのVPC Endpointが作成され、かつプライベートDNS名が有効化されていたことにより、リージョナルエンドポイントへのアクセスはVPC Endpoint経由になっていました。
- VPC Endpoint経由だと、STSリクエストの接続元IPはNAT GatewayのIPアドレスではなくVPCのプライベートIPとなり、Role Bの信頼ポリシーで許可しているIPとは異なるため、AssumeRoleが許可されませんでした。
- 作業用EC2では、古いAWS CLI(Ver1)を使用しており、デフォルトではグローバルエンドポイントを使用しているため、問題なくAssumeRoleができていました。
-
AWS CLI v1 は、デフォルトでは AWS STS リクエストをグローバル (レガシー) AWS STS エンドポイントに送信します。
(引用元: リージョナル AWS STS エンドポイントの使用方法 )
-
グローバルエンドポイント / リージョナルエンドポイントとは?
STSのエンドポイントは大きく二種類あります。
グローバルエンドポイントはSTSのリリース時に構築されたエンドポイントで、実体としてはus-east-1リージョンでホストされるSTSサービスです。
リージョナルエンドポイントは、リージョンごとに用意されたSTSエンドポイントです。
レイテンシー、耐障害性などの観点で、リージョナルエンドポイントの利用が推奨されています。
参考: リージョナル AWS STS エンドポイントの使用方法 | Amazon Web Services ブログ
※ただ、2025年からグローバルエンドポイントでも自動で同一リージョンで処理されるようになるアップデートがされるようです。
Starting in early 2025, requests to the STS global endpoint will be automatically served in the same Region as your AWS deployed workloads.
(引用元: Announcing upcoming changes to the AWS Security Token Service global endpoint | AWS Security Blog )
VPC Endpoint / プライベートDNSとは?
VPC EndpointをVPC内に作成すると、AWSの各種サービスに対してパブリックなインターネットではなくAWSの内部ネットワーク経由で接続することができ、セキュアになります。
インターフェイス型のVPC EndpointにはプライベートDNS名有効化オプションがあり、有効化することでパブリックなAWSサービスへのリクエストがVPCエンドポイントに解決されるようになります。VPCエンドポイント経由のリクエストでは、リクエスト先から見た接続元IPはVPCのプライベートIP(具体的にはENIのIP)になります。
参考:
-
プライベート DNS 名を有効にする
AWS のサービスの VPC エンドポイントに対してプライベート DNS ホスト名を有効にすることをお勧めします。これにより、 AWS SDK を介して行われたリクエストなど、パブリックサービスエンドポイントを使用するリクエストが VPC エンドポイントに解決されます。
- テクニカルインストラクターと学ぶインターフェース型とゲートウェイ型の VPC エンドポイント - builders.flash☆ - 変化を求めるデベロッパーを応援するウェブマガジン | AWS
調査検証と対応方針検討の記録
ここからは原因調査と対応方針策定までの流れをまとめます。
ネットワーク確認
ECSタスク実行時のサブネットとルートテーブルを確認し、 0.0.0.0/0 がNAT Gatewayに向いていることを確認しました。
CloudTrailの確認
sts:AssumeRoleがFailしているので、このAPIリクエストの詳細を確認するためにCloudTrail ログを見てみます。
CloudTrailのEvent Historyを、 Event Name = AssumeRole と 日時(Taskの実行日時) でフィルタして確認しました。

- 成功時イベント : regionがus-east-1、
sourceIPAddressがNATのグローバルIPだった - 失敗時イベント : regionがap-northeast-1、
sourceIPAddressが内部IP、かつvpcEndpointIdの値があった
失敗時のイベントのログを確認したことで、STSリクエストがVPC Endpoint経由になっていることが分かりました。
※ちなみに、CloudTrailでは、STSのログは対応するリージョンに切り替えないと見ることができないので注意です。例えばグローバルエンドポイントへのログはus-east-1に切り替えないと見られません。
Boto3のリポジトリを確認し、endpointに関係するIssueや変更履歴がないか確認
Pythonのバージョン前後で挙動が変わったことからライブラリのバージョン変更に原因がありそうなので、Boto3のrepositoryを見てみます。
以下のIssueとCHANGE LOGを見つけました。
-
https://github.com/boto/boto3/issues/4583
In Boto3 v1.40.0, we updated the default AWS STS endpoints resolution from legacy (global endpoint) to regional. This aligns with AWS best practices and brings boto3 in line with other AWS SDKs that already default to regional endpoints.
-
https://github.com/boto/boto3/blob/a331af728a28dac87652c1d40ff31e14466f81fb/CHANGELOG.rst#L424
1.40.0 ====== (略) * feature:``sts``: [``botocore``] Updated the default sts service endpoint from ``legacy`` to ``regional``. This behavior can be overridden using the ``sts_regional_endpoints`` setting in the shared AWS config file or the ``AWS_STS_REGIONAL_ENDPOINTS`` environment variable.
記載内容から、v1.40.0からデフォルトのSTSエンドポイントがグローバルからリージョナルになったことが分かりました。
バッチのBoto3のバージョンが変わったことを確認
念の為、バッチのBoto3のバージョンを確認します。 poetry.lock の差分を見ると、変更前はバージョン1.40未満かつ変更後はバージョン1.40以上となっていることが確認できました。

STSのエンドポイントについてドキュメントを確認
STSのエンドポイントについて公式ドキュメントなどをあたって理解を深めます。
- AWS STS Regional endpoints - AWS SDKs and Tools
- リージョナル AWS STS エンドポイントの使用方法 | Amazon Web Services ブログ
- AWS STS の VPC エンドポイントを作成する
ここまでで、リクエストがVPC Endpoint経由になっている理由が、
- Boto3のバージョンが変わってリージョナルエンドポイントを向くようになったこと
- 本バッチのVPCではVPC EndpointがプライベートDNS有効化された状態で作成されていること
だと分かりました。
今回の対応と背景
原因が分かったので、対応策を考えます。解決策としては以下が挙げられます。
- コードを変更し、明示的にグローバルエンドポイントを利用する
- メリット: これまでと同じ接続方法を続けることができ、影響範囲もコード修正箇所に限定できる
- デメリット: STSの推奨プラクティスとは異なる
- 信頼ポリシーでsourceVpceを設定する
- リージョナルエンドポイントを使いつつ、IAM Roleの信頼ポリシーで制限の掛け方を変える
- 例)
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, "Action": "sts:AssumeRole", "Condition": { "StringEquals": { "aws:SourceVpce": "vpce-xxxxxxxxxxx" } } } ] } - メリット: リージョナルエンドポイントを使うので、STSの推奨プラクティスに沿うことができる。
- デメリット: クロスアカウント先のアカウントBが社外アカウントのため、調整の手間とリードタイムがある。
- リージョナルエンドポイントを使いつつ、IAM Roleの信頼ポリシーで制限の掛け方を変える
- 設定でデフォルトエンドポイントを変更する
- AWSのconfig fileの
sts_regional_endpoints値や環境変数AWS_STS_REGIONAL_ENDPOINTS値を指定することで、グローバルエンドポイントを使用するように変更できる - デメリット: 同じECSタスク定義で行う他の処理に影響する可能性がある
- AWSのconfig fileの
- VPC Endpointを使用しない
- VPC Endpointを使わなければ、リクエストはパブリックなインターネット経由、つまり今回の環境だとNAT Gateway経由になります。
- デメリット: VPCのネットワーク設定の大きな変更となり、ECSタスク以外にも影響が出る可能性があります。更に、今回の環境では特定の機能を使うためにVPC Endpointを使用している背景があり、この手段は取れませんでした。
今回は
- 日次バッチが落ちてしまい、急ぎ修正を行う必要があったこと
- 他の処理への影響範囲を最小限にしたいこと
- 本処理が日次バッチであり、過度に耐障害性やレスポンスを気にする必要がないこと
などから、「STSリクエストはリージョナルエンドポイントを推奨」というAWSの方針とは異なるものの、一次対応として1を選択しました。
ただし、理想的には2の対応をすべきなので、社外と調整を進めていく予定です。
最後に
古めのシステムや環境をアップグレードするときは、関連するライブラリで大きな挙動の変更がないか確認しましょう。
AWS環境での障害の原因調査にはCloudTrailが便利でした。皆さんもぜひ使ってみてください。
Discussion