背景・目的
インターネットに疎通できない VPC 上の EC2 インスタンスで、SSM Patch Manager によるパッチ自動更新が失敗していました。
原因を調査したところ、S3 Gateway Endpoint の Endpoint Policy に設定された aws:PrincipalAccount 条件が、OS のパッケージマネージャ(yum)による anonymous リクエストを拒否していることがわかりました。
本記事では、SSM Agent の通信アーキテクチャを整理し、なぜこの問題が起きるのか、どう対策するかをまとめます。
まとめ
| 項目 | 内容 |
|---|---|
| 事象 | SSM Patch Manager でパッチ適用が失敗する |
| 原因 | S3 Gateway Endpoint の aws:PrincipalAccount 条件が yum の anonymous リクエストを拒否 |
| 根本理由 | yum は AWS 署名なし(anonymous HTTP)でリポジトリにアクセスするため、IAM の Principal 条件が評価不能 |
| 対策 | Endpoint Policy にリポジトリバケットへの条件なし許可を追加 |
| 影響範囲 | パッチ取得のみ。SSM Agent 自体の通信(コマンド受信・セッション等)は IAM 認証付きなので影響なし |
概要
登場人物の整理
AWS Systems Manager(SSM)は EC2 の運用管理サービスの総称で、その中に複数の機能があります。本記事で関係するのは以下の 2 つです。
| SSM Agent | SSM Patch Manager | |
|---|---|---|
| 何か | マネージドノード上で動くソフトウェア(デーモン) | AWS Systems Manager の機能/サービス |
| 役割 | SSM サービスからの指示を受けて実行する「手足」 | パッチ適用を管理・指示する「司令塔」 |
| 動く場所 | EC2、オンプレミスサーバー、エッジデバイス | AWS マネジメントコンソール / API |
Patch Manager(クラウド側)
「このベースラインに従ってパッチを当てろ」
│
▼ AWS-RunPatchBaseline コマンドを送信
SSM Agent(EC2 上)
「了解、スクリプト取得して yum update 実行します」
│
▼
OS (yum/dnf)
「パッケージダウンロードします」
SSM Agent
AWS Systems Manager Agent(SSM Agent)は、EC2 インスタンスやオンプレミスサーバー上にインストールされる Amazon のソフトウェアです。Systems Manager サービスからのリクエストを処理し、実行結果を返送します。Amazon Linux 2/2023 の AMI にはプリインストールされています。
SSM Agent のアーキテクチャ
SSM Agent は EC2 上で動作するデーモンで、全ての通信をアウトバウンドで開始します。インバウンドポートは一切不要です。
┌──────────┐ HTTPS (443) アウトバウンドのみ
│ EC2 │ ──────────────────────────────────────→ AWS Systems Manager
│(SSM Agent)│ ←── ロングポーリングでコマンド受信
└──────────┘
クライアントから EC2 にアクセスする流れ(Session Manager)
① クライアント(AWS CLI / Console)
→ AWS Systems Manager にセッション開始をリクエスト
│
▼
② AWS Systems Manager が EC2 上の Agent 向けにセッション情報を作成
│
▼
③ EC2 上の SSM Agent がポーリングでセッション開始を検知
│
▼
④ SSM Agent が ssmmessages endpoint 経由で WebSocket チャネルを確立
│
▼
⑤ クライアント ↔ AWS Systems Manager ↔ SSM Agent で双方向通信成立
SSH との違い: SSH はインバウンド 22 番ポートが必要だが、Session Manager は SSM Agent が常にアウトバウンドでポーリングしているため、外からポートを開ける必要がない。
プライベートサブネットで必要な VPC Endpoints
| VPC Endpoint | タイプ | 用途 |
|---|---|---|
com.amazonaws.<region>.ssm |
Interface | SSM API(登録、Association 取得) |
com.amazonaws.<region>.ec2messages |
Interface | コマンド受信(ロングポーリング) |
com.amazonaws.<region>.ssmmessages |
Interface | Session Manager / 結果返送(WebSocket) |
com.amazonaws.<region>.s3 |
Gateway | パッチスクリプト・リポジトリアクセス |
Patch Manager
Patch Manager は「いつ・何を・どのインスタンスに」パッチを当てるかを決める仕組みで、実際の実行は SSM Agent が担います。SSM Agent は Patch Manager 以外にも Run Command や Session Manager など、SSM の様々な機能の実行エンジンとして動きます。
※ 本記事では EC2(クラウド上)の構成に限定して説明します。
Patch Manager 実行時の通信フロー
① SSM Agent → ec2messages endpoint
「新しいコマンドある?」(ポーリング)
② SSM Service → SSM Agent
「AWS-RunPatchBaseline を実行せよ」
③ SSM Agent → S3 (aws-patch-manager-<region>-xxx)
「パッチ適用スクリプト(AWS-RunPatchBaseline の実行コード)をダウンロード」
→ インスタンスプロファイルの認証情報で署名付きリクエスト ✅
④ SSM Agent → S3 (patch-baseline-snapshot-<region>)
「パッチベースライン(どのパッチを当てるかのルール定義)を取得」
→ インスタンスプロファイルの認証情報で署名付きリクエスト ✅
⑤ SSM Agent が OS の yum/dnf を呼び出し
⑥ yum → S3 (amazonlinux-2-repos-<region>)
「RPM パッケージをダウンロード」
→ ★ anonymous HTTP(署名なし)★ ← ここが失敗する
⑦ SSM Agent → ssmmessages endpoint
「パッチ適用結果を報告」
実践
事前準備
環境を構築します
┌─────────────────────────────────────────────────────────────────────────────┐
│ VPC: vpc-XXXXXXXXXXXXXXXXX (10.0.0.0/16) │
│ ※ Internet Gateway なし / NAT Gateway なし │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Private Subnet: subnet-XXXXXXXXXXXXXXXXX (10.0.0.0/16, ap-northeast-1a)│ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────────────────────────┐ │ │
│ │ │ EC2 │ │ Interface Endpoints (ENI) │ │ │
│ │ │ i-XXXXXXXX│ │ │ │ │
│ │ │ │ 443 │ eni: ssm.ap-northeast-1 │ │ │
│ │ │ Amazon Linux 2 │─────→│ eni: ssmmessages.ap-northeast-1 │ │ │
│ │ │ SSM Agent │ │ eni: ec2messages.ap-northeast-1 │ │ │
│ │ │ t3.micro │ │ │ │ │
│ │ │ │ │ SG: inbound 443 from 10.0.0.0/16 │ │ │
│ │ └────────┬─────────┘ └──────────────────────────────────────┘ │ │
│ │ │ │ │
│ └───────────┼────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Route Table → prefix list (S3) │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ S3 Gateway Endpoint: vpce-XXXXXXXXXXXXXXXXX │ │
│ │ │ │
│ │ Policy: │ │
│ │ Allow s3:* IF aws:PrincipalAccount == XXXXXXXXXXXX │ │
│ │ │ │
│ │ → IAM 認証付きリクエスト(SSM Agent) ✅ 通る │ │
│ │ → anonymous リクエスト(yum) ❌ 拒否される │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
│
▼
┌───────────────────────────┐
│ Amazon S3 │
│ │
│ • aws-patch-manager-* │ ← IAM認証 ✅
│ • patch-baseline-snapshot-*│ ← IAM認証 ✅
│ • amazonlinux-2-repos-* │ ← anonymous ❌
└───────────────────────────┘
1.CDKテンプレートを作成します
2.CDKデプロイします
npx cdk deploy
3.リソースを確認します。(内容は簡略化しています)
-- VPC
aws ec2 describe-vpcs --vpc-ids vpc-065e465889045133c --region ap-northeast-1 --output json
{
"VpcId": "vpc-065e465889045133c",
"CidrBlock": "10.0.0.0/16",
"IsDefault": false,
"State": "available"
}
-- サブネット
aws ec2 describe-subnets --filters \"Name=vpc-id,Values=vpc-065e465889045133c\" --region ap-northeast-1 --output json
{
"SubnetId": "subnet-0fe02d4a96b4b602b",
"CidrBlock": "10.0.0.0/16",
"AvailabilityZone": "ap-northeast-1a",
"MapPublicIpOnLaunch": false
}
---GW
aws ec2 describe-internet-gateways --filters \"Name=attachment.vpc-id,Values=vpc-065e465889045133c\" --region ap-northeast-1 --output json
aws ec2 describe-nat-gateways --filter \"Name=vpc-id,Values=vpc-065e465889045133c\" --region ap-northeast-1 --output json
{ "InternetGateways": [] }
{ "NatGateways": [] }
---VPCe
aws ec2 describe-vpc-endpoints --filters \"Name=vpc-id,Values=vpc-065e465889045133c\" --region ap-northeast-1 --output json
[
{
"VpcEndpointId": "vpce-XXXXXXXXXXXXXXXXXX",
"ServiceName": "com.amazonaws.ap-northeast-1.s3",
"VpcEndpointType": "Gateway",
"State": "available",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "AWS": "*" },
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalAccount": "{アカウントID}"
}
}
}
]
}
},
{
"VpcEndpointId": "vpce-XXXXXXXXXXXXXXXXXX",
"ServiceName": "com.amazonaws.ap-northeast-1.ssm",
"VpcEndpointType": "Interface",
"State": "available",
"PrivateDnsEnabled": true
},
{
"VpcEndpointId": "vpce-XXXXXXXXXXXXXXXXXX",
"ServiceName": "com.amazonaws.ap-northeast-1.ssmmessages",
"VpcEndpointType": "Interface",
"State": "available",
"PrivateDnsEnabled": true
},
{
"VpcEndpointId": "vpce-XXXXXXXXXXXXXXXXXX",
"ServiceName": "com.amazonaws.ap-northeast-1.ec2messages",
"VpcEndpointType": "Interface",
"State": "available",
"PrivateDnsEnabled": true
}
]
Phase 1: 原因の特定
1-1. EC2 上で yum を実行して切り分け
まず EC2 に Session Manager で接続し、yum を実行して事象を再現します。
sh-4.2$ sudo yum update --security
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
Could not retrieve mirrorlist https://amazonlinux-2-repos-ap-northeast-1.s3.dualstack.ap-northeast-1.amazonaws.com/2/core/latest/x86_64/mirror.list error was
14: HTTPS Error 403 - Forbidden
One of the configured repositories failed (Unknown),
and yum doesn't have enough cached data to continue. At this point the only
safe thing yum can do is fail. There are a few ways to work "fix" this:
1. Contact the upstream for the repository and get them to fix the problem.
2. Reconfigure the baseurl/etc. for the repository, to point to a working
upstream. This is most often useful if you are using a newer
distribution release than is supported by the repository (and the
packages for the previous distribution release still work).
3. Run the command with the repository temporarily disabled
yum --disablerepo=<repoid> ...
4. Disable the repository permanently, so yum won't use it by default. Yum
will then just ignore the repository until you permanently enable it
again or use --enablerepo for temporary usage:
yum-config-manager --disable <repoid>
or
subscription-manager repos --disable=<repoid>
5. Configure the failing repository to be skipped, if it is unavailable.
Note that yum will try to contact the repo. when it runs most commands,
so will have to try and fail each time (and thus. yum will be be much
slower). If it is a very temporary problem though, this is often a nice
compromise:
yum-config-manager --save --setopt=<repoid>.skip_if_unavailable=true
Cannot find a valid baseurl for repo: amzn2-core/2/x86_64
sh-4.2$
https://amazonlinux-2-repos-ap-northeast-1.s3.dualstack.ap-northeast-1.amazonaws.com/2/core/latest/x86_64/mirror.listから、S3 上のリポジトリにアクセスできていないことがわかります。
この VPC はインターネット疎通がないため、S3 へのアクセスは Gateway Endpoint 経由のはずです。
1-2. S3 Gateway Endpoint Policy を確認
現状の設定を特定します
aws ec2 describe-vpc-endpoints \
--filters "Name=service-name,Values=com.amazonaws.ap-northeast-1.s3" \
--query "VpcEndpoints[*].{VpcId:VpcId,VpcEndpointId:VpcEndpointId,PolicyDocument:PolicyDocument}"
確認結果(dms-network-vpc に紐づく Endpoint):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalAccount": "XXXXXXXXXXXX"
}
}
}
]
}
aws:PrincipalAccount 条件があります。yum は AWS 署名なし(anonymous)でリポジトリにアクセスするため、 この条件で拒否されている可能性が高いと仮説を立てます。
1-3.仮説の検証:条件を一時的に外して yum を再実行
Endpoint Policy から条件を外して再度 yum を実行します。
1.下記のコマンドで条件を外します
aws ec2 modify-vpc-endpoint --vpc-endpoint-id vpce-XXXXXXXXXXX --policy-document \"{\\\"Version\\\":\\\"2012-10-17\\\",\\\"Statement\\\":[{\\\"Effect\\\":\\\"Allow\\\",\\\"Principal\\\":\\\"*\\\",\\\"Action\\\":\\\"s3:*\\\",\\\"Resource\\\":\\\"*\\\"}]}\" --region ap-northeast-1
{
}
2.変更後の状態を確認します。aws:PrincipalAccount 条件が外れています。
aws ec2 describe-vpc-endpoints --vpc-endpoint-ids vpce-03e623bfd28c4da25 --query \"VpcEndpoints[0].PolicyDocument\" --region ap-northeast-1
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": "*"
}
]
}
3.再びセッションマネージャーで試します。成功しました
$ sudo yum update --security
sh-4.2$ sudo yum update --security
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core | 3.6 kB 00:00:00
amzn2extra-docker | 2.9 kB 00:00:00
amzn2extra-kernel-5.10 | 3.0 kB 00:00:00
(1/7): amzn2-core/2/x86_64/group_gz | 2.7 kB 00:00:00
(2/7): amzn2-core/2/x86_64/updateinfo | 1.3 MB 00:00:00
(3/7): amzn2extra-docker/2/x86_64/updateinfo | 36 kB 00:00:00
(4/7): amzn2extra-kernel-5.10/2/x86_64/updateinfo | 201 kB 00:00:00
(5/7): amzn2extra-docker/2/x86_64/primary_db | 162 kB 00:00:00
(6/7): amzn2extra-kernel-5.10/2/x86_64/primary_db | 46 MB 00:00:00
(7/7): amzn2-core/2/x86_64/primary_db | 85 MB 00:00:01
No packages needed for security; 0 packages available
No packages marked for update
sh-4.2$
→ 成功。Endpoint Policy の aws:PrincipalAccount 条件が原因と確定しました。
1-4. なぜこのポリシーで失敗するのか
| リクエスト元 | 認証方式 |
aws:PrincipalAccount 評価 |
結果 |
|---|---|---|---|
| SSM Agent(スクリプト取得) | IAM 認証(SigV4 署名あり) |
{AWSアカウントID} → 条件一致 |
✅ 許可 |
| yum(パッケージ取得) | anonymous(署名なし) | Principal 情報なし → 条件評価不能 | ❌ 暗黙的 Deny |
Amazon Linux のパッケージリポジトリは S3 上のパブリックバケットにホストされています。yum update は AWS SDK を使わず素の HTTP リクエストでパッケージを取得するため、AWS 署名が付きません。
Phase 2: 対策の実施
2-1. 対策案の比較
| 案 | 内容 | メリット | デメリット |
|---|---|---|---|
| ① VPC を変更 |
IGW/NATGWを保有するVPC に移行 |
標準構成に統一できる。許されるなら | 既存作業への影響大 |
| ② Endpoint Policy を修正 | anonymous アクセスを許可する Statement を追加 | 影響範囲が小さい | Endpoint Policy の管理が必要 |
今回は 案② を採用します。
2-2. 修正後の Endpoint Policy
既存の条件付き Statement はそのまま残し、yum リポジトリへの anonymous アクセスのみ許可する Statement を追加します。
- CDKを修正し、デプロイします
- 設定変更後の確認をします。条件付き + リポジトリ許可の 2 Statement 構成になっています。
aws ec2 describe-vpc-endpoints --vpc-endpoint-ids vpce-XXXXXXXXXX --query \"VpcEndpoints[0].PolicyDocument\" --region ap-northeast-1
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAccountPrincipals",
"Effect": "Allow",
"Principal": { "AWS": "*" },
"Action": "s3:*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalAccount": "アカウントID"
}
}
},
{
"Sid": "AllowAnonymousPatchRepoAccess",
"Effect": "Allow",
"Principal": { "AWS": "*" },
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::al2023-repos-ap-northeast-1-de612dc2/*",
"arn:aws:s3:::amazonlinux-2-repos-ap-northeast-1/*"
]
}
]
}
Phase 3: 動作確認
- コマンドを打って到達するか確認します。問題なく通りました
sh-4.2$ sudo yum update --security
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core | 3.6 kB 00:00:00
No packages needed for security; 0 packages available
No packages marked for update
sh-4.2$
ポイント:
- 既存の
aws:PrincipalAccount条件はそのまま維持 - 追加 Statement は
s3:GetObjectのみ、リポジトリバケットに限定 - OS が Amazon Linux 2 なら
amazonlinux-2-repos-*、AL2023 ならal2023-repos-*が必要
考察
なぜ anonymous リクエストになるのか
SSM Patch Manager の処理は 2 層に分かれています。
- SSM Agent 層: AWS SDK(Go)を使って S3 からスクリプトやベースラインを取得する。IAM 認証付き。
-
OS 層: SSM Agent が
yum update等の OS コマンドを呼び出す。yum は AWS SDK を使わず、/etc/yum.repos.d/に定義された URL に素の HTTP でアクセスする。
この 2 層構造のため、同じ S3 Gateway Endpoint を通るにもかかわらず、認証方式が異なります。Endpoint Policy で aws:PrincipalAccount を使うと、OS 層のアクセスだけが拒否されるという非直感的な挙動になります。
Data Exfiltration リスクについて
aws:PrincipalAccount 条件を完全に外すと、VPC 内の侵害されたインスタンスが外部の S3 バケットにデータを送信できるリスクがあります。今回の対策では条件付き Statement を残し、リポジトリバケットへの GetObject のみを追加許可しているため、Data Exfiltration 対策は維持されます
SSM Agent の通信まとめ
| 通信先 | プロトコル | 認証 | VPC Endpoint タイプ |
|---|---|---|---|
| ssm endpoint | HTTPS/443 | IAM (SigV4) | Interface |
| ec2messages endpoint | HTTPS/443 | IAM (SigV4) | Interface |
| ssmmessages endpoint | HTTPS/443 (WebSocket) | IAM (SigV4) | Interface |
| S3(SSM 関連バケット) | HTTPS/443 | IAM (SigV4) | Gateway |
| S3(OS リポジトリ) | HTTP or HTTPS | anonymous | Gateway |
参考
- SSM Agent communications with AWS managed S3 buckets
- Reference: Amazon S3 buckets for patching operations
- Improve the security of EC2 instances by using VPC endpoints for Systems Manager
- Update yum on my Amazon Linux EC2 instance without internet access
- Reference: ec2messages, ssmmessages, and other API operations
