こんにちはますのです。
S3エンドポイントポリシーをめちゃくちゃ絞った結果、うまくいかないことありますよね??
VPC から S3 への通信を「許可した AWS アカウントのバケットだけ」に絞るため、S3 ゲートウェイエンドポイントにエンドポイントポリシーを設定しました。Amazon Linux のリポジトリ、SSM Agent、ECR のレイヤーといった AWS 管理のバケットは事前に洗い出して許可し、S3 の読み書きや ECR からのイメージ取得もテストで確認しました。
完璧。と思ってリリースしたのもつかの間、Github Actions ランナーが失敗するようになりました。
何が不足だったのか??を確かめに、エンドポイントポリシーに関して調査を進めました。
背景
- AWS CodeBuild のマネージド GitHub Actions ランナー(CodeBuild-hosted GitHub Actions runner)を VPC 内で動かしていた
- S3エンドポイントポリシーで制限を入れる前までは問題なく動いていた
- S3エンドポイントポリシーを記載した途端にジョブがランナーの起動直後に失敗した
概要図
やりたかったこと
もともと VPC 内のワークロードは NAT ゲートウェイ経由で S3 にアクセスしていました。
これを S3 ゲートウェイエンドポイント経由に切り替えて、次の2つを狙いました。
- 通信を AWS のネットワーク内に留める
- アクセスできる S3 バケットを自分たちが管理する AWS アカウントのものだけに絞る
アカウントで絞る部分は s3:ResourceAccount(アクセス先のバケットを所有するアカウント)の条件で書いています。
しかし、完全に制限してしまうと問題が発生することは、過去同じ対応をして既に踏みぬき学んでいるわたしです。
AWS のサービスが裏で使っているAWS 管理のバケットへのアクセスまで書いていくことで未然に防ぐおまじないをいれてあります。
| 用途 | バケット(東京リージョン) | 止まると起きること |
|---|---|---|
| Amazon Linux のパッケージリポジトリ |
al2023-repos-ap-northeast-1-de612dc2、amazonlinux-2-repos-ap-northeast-1 ほか |
dnf / yum でのパッケージ取得・更新ができない |
| SSM Agent |
amazon-ssm-ap-northeast-1、aws-ssm-ap-northeast-1、ap-northeast-1-birdwatcher-prod ほか |
Agent の更新や Patch Manager が失敗する |
| ECR のイメージレイヤー | prod-ap-northeast-1-starport-layer-bucket |
ECR からイメージを pull できない |
ポリシーを入れたあとに確認したのは次の項目です。ここまではすべて通っていました。
| テスト項目 | 結果 |
|---|---|
| Dockerfile のベースイメージを AWS 管理の ECR から取得できる | OK |
| 許可したアカウントのバケットに put / get / list できる | OK |
| 許可していないアカウントのバケットにはアクセスできない | AccessDenied(期待どおり) |
うん、完璧ですね。そう思って自信満々に仕事を終わらせたと思っていました。
結論
最終的に設定しているエンドポイントポリシーとしては以下になります。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AmazonLinuxAMIRepositoryAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::al2023-repos-ap-northeast-1-de612dc2/*",
"arn:aws:s3:::amazonlinux-2-repos-ap-northeast-1/*",
"arn:aws:s3:::amazonlinux.ap-northeast-1.amazonaws.com/*",
"arn:aws:s3:::packages.*.amazonaws.com/*",
"arn:aws:s3:::repo.*.amazonaws.com/*"
]
},
{
"Sid": "SSMagentAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::amazon-ssm-ap-northeast-1/*",
"arn:aws:s3:::amazon-ssm-packages-ap-northeast-1/*",
"arn:aws:s3:::ap-northeast-1-birdwatcher-prod/*",
"arn:aws:s3:::aws-ssm-ap-northeast-1/*",
"arn:aws:s3:::aws-windows-downloads-ap-northeast-1/*",
"arn:aws:s3:::patch-baseline-snapshot-ap-northeast-1/*"
]
},
{
"Sid": "ECRAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::prod-ap-northeast-1-starport-layer-bucket/*"
},
{
"Sid": "AWSAccountAccess",
"Effect": "Allow",
"Principal": "*",
"Action": "*",
"Resource": "*",
"Condition": {
"StringEquals": {
"s3:ResourceAccount": [
"123456789012"
]
}
}
},
{
"Sid": "GitHubActionsSelfHostedRunnerBinary",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::codefactory-ap-northeast-1-prod-default-build-agent-executor/*"
}
]
}
不足していた箇所
codefactory-ap-northeast-1-prod-default-build-agent-executor
不足していた点を確認
動作に必要な GHA self-hosted runner binary ファイル (actions-runner-linux-x64.tar.gz) を AWS サービス側で管理している S3 バケット codefactory-ap-northeast-1-prod-default-build-agent-executor から取得する動作となっていました。
慌てふためくわたし。
エンドポイントポリシーにarn:aws:s3:::codefactory-ap-northeast-1-prod-default-build-agent-executor/*へのs3:GetObjectを追加して改善に至りました。
調査概要
結論は先述したとおりです。
ココから先は、症状・切り戻し・原因と、その許可範囲が妥当かの検討を書きます。
症状
ポリシーを入れて数日後、GitHub Actions のワークフローを流したところ、CodeBuild のランナーが起動直後に失敗しました。ビルドログはこんな感じです(ID は伏せています)。
[Container] Ignoring BUILD phase commands for self-hosted runner build.
GHA self-hosted runner build triggered by /actions/runs/xxxxxxx/job/xxxxxxx
Creating GHA self-hosted runner workspace folder: actions-runner
Downloading GHA self-hosted runner binary
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 263 0 263 0 0 2354 0 --:--:-- --:--:-- --:--:-- 2369
gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error is not recoverable: exiting now
[Container] Phase complete: BUILD State: FAILED
[Container] Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: tar xzf ./actions-runner-linux-x64.tar.gz. Reason: exit status 2
ランナーのバイナリをダウンロードしたはずなのに、263 バイト しか落ちてきていません。中身は S3 が返したエラーのレスポンスで、それを tar.gz として展開しようとして失敗しています。
ダウンロード元がどこかはログに出ません。ただ、エンドポイントポリシーを入れてから起きているので、ポリシーが原因なのは明らかでした。
ポリシーを入れた直後に落ちなかったのは、CI/CD がたまにしか動かないからです。
今回の最大のミスとしては、CI/CD の全部のワークフローをテスト項目に入れていなかったことです。
数日後に初めて気付きました。
切り戻しと、その間に考えたこと
原因のバケットが分からないまま開発を止めておけないので、いったん切り戻しました。
- エンドポイントポリシーだけをフルアクセスに戻す
- ルートテーブルは変更後のまま(S3 宛てはエンドポイント経由のまま)
- 本番環境への適用は保留
ルートまで戻さなかったのは、切り分けの対象をポリシーだけに絞るためです。これでランナーが動けば、経路ではなくポリシーの問題だと確定できます。
原因
CodeBuild が GitHub Actions のランナーとして動くとき、ランナーのバイナリは GitHub からではなく、AWS 管理の S3 バケット codefactory-ap-northeast-1-prod-default-build-agent-executor から取得されます。
- このバケットの所有者は AWS なので、
s3:ResourceAccountの条件を満たさず拒否されます-
aws:PrincipalAccountで自アカウントに絞る書き方でも同じく拒否されます
-
- 事前に許可した Amazon Linux・SSM・ECR のバケットと同じ種類のものですが、AWS 公式ドキュメントに記載が見当たらず、洗い出しの段階では見つけられませんでした
- 修正は、このバケットへの
s3:GetObjectを足すことです。足したあとランナーが起動することを確認してから、本番環境にも適用しました
この許可範囲は妥当か
AWS 管理のバケットへの許可は、すべて Principal: * です。範囲が広すぎないかを確認しました。
| 観点 | 評価 |
|---|---|
| 許可する操作 | AWS 管理のバケットに対しては s3:GetObject だけ。書き込み(PutObject など)は許可アカウントのバケットにしかできない |
| 持ち出し | VPC の外へデータを書き出す経路は、許可アカウントのバケットに限られたまま。ポリシーの目的は崩れていない |
| バケット名のワイルドカード | ランナーのバケットは完全な名前で指定した。codefactory-* のようにワイルドカードにすると、第三者が同じ接頭辞で作ったバケットも読める。書き込みはできないので持ち出しにはならないが、許可する理由もない |
ワイルドカードについては、SSM のドキュメントも「リージョン部分をワイルドカードにすると意図しないバケットへのアクセスを許可しうるので避ける」としています。
バケット名は AWS 側の実装で、ドキュメントに載っていない以上、変わっても告知される保証はありません。同じ症状(ランナーのダウンロードが数百バイトで終わる)が再発したら、まずこのステートメントを疑ってください。
まとめ
- S3 のエンドポイントポリシーを許可アカウントに絞ると、AWS のサービスが裏で使う AWS 管理のバケットへのアクセスも止まる
- Amazon Linux・SSM・ECR のバケットはドキュメントで分かるが、CodeBuild の GitHub Actions ランナーのバイナリのバケット(
codefactory-<region>-prod-default-build-agent-executor)は載っていない - 読み込みだけを完全なバケット名で許可すれば、持ち出し対策というポリシーの目的を崩さずに済む
- ポリシーを絞ったら、CI/CD やパッチ適用のような「たまにしか動かない処理」までテストする
- エンドポイントポリシーは相変わらず奥が深い
