CT.STS.PV.1
コントロールの説明
組織外の IAM プリンシパル(IAM ユーザー、IAM ロール)による STS リソースへのアクセスを拒否する。
本コントロールは RCP(リソースコントロールポリシー)により、組織外の IAM プリンシパルからの sts:AssumeRole および sts:SetContext をブロックする。Control Tower で対象の OU を指定して有効化すると、その OU 配下の全アカウントの全 IAM ロールに適用される。
RCP テンプレートの Condition は CT.S3.PV.4 と同じ構造で、以下の 2 つの条件を AND で評価し、両方を満たす場合に拒否する。
aws:PrincipalIsAWSServiceがfalse(AWS サービスプリンシパルでない)aws:PrincipalOrgIDが自組織の ID と一致しない(組織外のプリンシパルである)
ただし、他の RCP 型コントロール(CT.S3.PV.4、CT.SQS.PV.1 等)が サービス名:* を対象とするのに対し、本コントロールは以下の 2 アクションのみを対象とする。
| 対象アクション | 説明 |
|---|---|
sts:AssumeRole | IAM ロールの引き受け |
sts:SetContext | セッションへのコンテキスト情報の設定 |
以下の STS アクションは明示的にスコープ外であり、コントロール有効化後も影響を受けない。
| スコープ外のアクション | 理由 |
|---|---|
sts:AssumeRoleWithSAML | AWS セキュリティ認証情報を使用せず、リクエストコンテキストに aws:PrincipalOrgID が含まれないため |
sts:AssumeRoleWithWebIdentity | 同上 |
sts:SetSourceIdentity | AssumeRoleWithSAML / AssumeRoleWithWebIdentity の拒否を防ぐため除外 |
sts:TagSession | 同上 |
sts:GetCallerIdentity | 権限不要の操作のため |
この設計により、以下のユースケースはコントロール有効化後も正常に動作する。
- IAM Identity Center(Entra ID SSO 等): STS API を経由せず、IAM Identity Center 独自の API(
sso:GetRoleCredentials)で一時認証情報を取得するためスコープ外(SDK の認証フローを参照) - GitHub Actions OIDC:
sts:AssumeRoleWithWebIdentityを使用 → スコープ外 - AWS サービスによるロール引き受け:
aws:PrincipalIsAWSService条件により除外
コントロール有効化時の動作をまとめると以下のようになる。
| 操作 | 組織内 | 組織外 |
|---|---|---|
sts:AssumeRole | 成功 | 拒否 |
sts:AssumeRoleWithSAML | 成功 | 成功(スコープ外) |
sts:AssumeRoleWithWebIdentity | 成功 | 成功(スコープ外) |
sso:GetRoleCredentials(IAM Identity Center) | 成功 | —(IAM Identity Center 経由のみ) |
パラメータは以下の 1 つ。
ExemptedPrincipalArns(任意): RCP の拒否対象外にする IAM プリンシパルの ARN。組織外のパートナーアカウントや外部サービスのロール等、特定のプリンシパルにアクセスを許可する場合に使用する
本コントロールは IAM ロールの信頼ポリシーを変更するのではなく、独立したアクセス制御レイヤーとして機能する。信頼ポリシーで組織外アカウントにロール引き受けを許可していても、本コントロールが有効な場合は RCP により拒否される。
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。
検証の流れ
flowchart LR
A[1. 事前準備] --> B[2. 組織内・組織外<br>両方成功]
B --> C[3. CT.STS.PV.1<br>有効化]
C --> D[4. 既存設定<br>維持を確認]
C --> E[5. 組織内アクセス<br>成功を確認]
C --> F[6. 組織外アクセス<br>拒否を確認]
C --> G[7. SSO ログイン<br>成功を確認]
C --> H[8. GitHub Actions OIDC<br>成功を確認]
D & E & F & G & H --> I[9. CloudTrail<br>で確認]
I --> J[10. CT.STS.PV.1<br>無効化]
J --> K[11. 組織外アクセス<br>成功を確認]
結果
- コントロールの有効化前、組織内・組織外の両方から
sts:AssumeRoleでロールを引き受けられることを確認できた。 - コントロールの有効化後、既存の信頼ポリシー(組織外アカウントへの許可)は維持されることを確認できた。
- 組織内プリンシパルからの
sts:AssumeRoleは引き続き成功することを確認できた。 - 組織外プリンシパルからの
sts:AssumeRoleが拒否されることを確認できた。ただし、エラーメッセージにはexplicit deny in a resource control policyは含まれず、CT.S3.PV.4 と同様に汎用的なAccessDeniedのみが返された。CT.SECRETSMANAGER.PV.1 や CT.KMS.PV.7、CT.SQS.PV.1 では詳細なエラーメッセージが返されたのとは対照的である。 - コントロール有効化中に IAM Identity Center(Entra ID SSO)で再ログインし、Workload アカウントのセッションが正常に取得できることを確認できた。IAM Identity Center は STS API を経由せず、独自の API(
sso:GetRoleCredentials)で一時認証情報を取得するため、CT.STS.PV.1 のスコープ外である。 - コントロール有効化中に GitHub Actions OIDC でデプロイを実行し、
sts:AssumeRoleWithWebIdentityが成功することを確認できた。CloudTrail でも成功イベントとして記録されていた。 - 拒否されたイベント(
sts:AssumeRole)は、呼び出し元である組織外アカウントの CloudTrail に管理イベントとして記録されることを確認できた。Workload アカウントの CloudTrail や、管理アカウントの Organization Trail(CloudWatch Logs)には記録されなかった。CT.KMS.PV.7 と同じ挙動であり、CT.SECRETSMANAGER.PV.1 では両方の CloudTrail で確認できたのとは対照的である。 - コントロールの無効化後、再び組織外プリンシパルからロールを引き受けられることを確認できた。
1. 事前準備
テスト用の IAM ロールを作成する。信頼ポリシーで組織外アカウントからの sts:AssumeRole を許可する。
aws iam create-role \
--role-name ct-sts-pv1-test \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSameAccount",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::<Workload のアカウント ID>:root"},
"Action": "sts:AssumeRole"
},
{
"Sid": "AllowExternalAccount",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::<組織外アカウント ID>:user/<IAM ユーザー名>"},
"Action": "sts:AssumeRole"
}
]
}' \
--query 'Role.Arn'"arn:aws:iam::<Workload のアカウント ID>:role/ct-sts-pv1-test"2. コントロール有効化前の確認
組織内アクセス(成功)
aws sts assume-role \
--role-arn arn:aws:iam::<Workload のアカウント ID>:role/ct-sts-pv1-test \
--role-session-name internal-test \
--query 'Credentials.{AccessKeyId:AccessKeyId,Expiration:Expiration}'{
"AccessKeyId": "<アクセスキー ID>",
"Expiration": "<有効期限>"
}組織外アクセス(成功)
aws sts assume-role \
--role-arn arn:aws:iam::<Workload のアカウント ID>:role/ct-sts-pv1-test \
--role-session-name external-test \
--query 'Credentials.{AccessKeyId:AccessKeyId,Expiration:Expiration}' \
--region ap-northeast-1 \
--profile <組織外プロファイル名>{
"AccessKeyId": "<アクセスキー ID>",
"Expiration": "<有効期限>"
}信頼ポリシーにより、組織外アカウントからもロールを引き受けられた。
3. CT.STS.PV.1 の有効化
Control Tower の管理アカウントで、対象の OU にコントロールが有効になっていないことを確認する。
aws controltower list-enabled-controls \
--target-identifier <OU の ARN> \
--profile Master{
"enabledControls": []
}コントロールを有効化する。
aws controltower enable-control \
--control-identifier arn:aws:controlcatalog:::control/aqnqv7jjgi2dtl6r1v12xglio \
--target-identifier <OU の ARN> \
--profile Master{
"arn": "arn:aws:controltower:ap-northeast-1:<管理アカウント ID>:enabledcontrol/<enabledcontrol ID>",
"operationIdentifier": "<オペレーション ID>"
}有効化が完了するまで待機する。
aws controltower get-control-operation \
--operation-identifier <オペレーション ID> \
--query 'controlOperation.{operationType:operationType,status:status,statusMessage:statusMessage}' \
--profile Master{
"operationType": "ENABLE_CONTROL",
"status": "SUCCEEDED",
"statusMessage": "Operation was successful."
}4. 既存設定の維持確認
RCP は信頼ポリシーを変更するのではなく、アクセス制御のレイヤーとして機能する。信頼ポリシーが自動的に変更されていないことを確認する。
aws iam get-role \
--role-name ct-sts-pv1-test \
--query 'Role.AssumeRolePolicyDocument'{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSameAccount",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<Workload のアカウント ID>:root"
},
"Action": "sts:AssumeRole"
},
{
"Sid": "AllowExternalAccount",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<組織外アカウント ID>:user/<IAM ユーザー名>"
},
"Action": "sts:AssumeRole"
}
]
}信頼ポリシーは変更されていない。組織外アカウントへの許可が残っているが、RCP により実際のアクセスは拒否される。
5. 組織内アクセスの確認
組織内プリンシパルからの sts:AssumeRole は引き続き成功する。
aws sts assume-role \
--role-arn arn:aws:iam::<Workload のアカウント ID>:role/ct-sts-pv1-test \
--role-session-name internal-test-after-enable \
--query 'Credentials.{AccessKeyId:AccessKeyId,Expiration:Expiration}'{
"AccessKeyId": "<アクセスキー ID>",
"Expiration": "<有効期限>"
}6. 組織外アクセスの拒否確認
aws sts assume-role \
--role-arn arn:aws:iam::<Workload のアカウント ID>:role/ct-sts-pv1-test \
--role-session-name external-test-after-enable \
--region ap-northeast-1 \
--profile <組織外プロファイル名>An error occurred (AccessDenied) when calling the AssumeRole operation:
User: arn:aws:iam::<組織外アカウント ID>:user/<IAM ユーザー名>
is not authorized to perform: sts:AssumeRole on resource:
arn:aws:iam::<Workload のアカウント ID>:role/ct-sts-pv1-test信頼ポリシーで許可されているにもかかわらず、組織外プリンシパルからの sts:AssumeRole が拒否された。CT.SECRETSMANAGER.PV.1 や CT.KMS.PV.7、CT.SQS.PV.1 では explicit deny in a resource control policy を含む詳細なエラーメッセージが返されたが、STS では CT.S3.PV.4 と同様に汎用的な AccessDenied のみが返された。
7. SSO ログインの確認(sso:GetRoleCredentials)
IAM Identity Center(Entra ID SSO)がコントロールの影響を受けないことを確認する。IAM Identity Center の aws sso login は sts:AssumeRoleWithSAML ではなく、IAM Identity Center 独自の API(sso:GetRoleCredentials)を使用して一時認証情報を取得する(SDK の認証フローを参照)。STS API を経由しないため、CT.STS.PV.1 のスコープ外である。
コントロール有効化中に再ログインする。
aws sso login --profile Workloadログイン完了後、セッションが有効であることを確認する。
aws sts get-caller-identity --profile Workload{
"UserId": "<ユーザー ID>",
"Account": "<Workload のアカウント ID>",
"Arn": "arn:aws:sts::<Workload のアカウント ID>:assumed-role/<SSO ロール名>/<セッション名>"
}コントロール有効化中でも SSO ログインが成功し、Workload アカウントのセッションが正常に取得できた。
8. GitHub Actions OIDC の確認(AssumeRoleWithWebIdentity)
sts:AssumeRoleWithWebIdentity がスコープ外であることを確認する。コントロール有効化中に GitHub Actions のデプロイを実行する。
git commit --allow-empty -m "CT.STS.PV.1: Verify OIDC deploy during control enabled"
git pushGitHub Actions のデプロイジョブが成功した後、CloudTrail で AssumeRoleWithWebIdentity の成功イベントを確認する。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--start-time <検索開始時刻> \
--end-time <検索終了時刻> \
--query 'Events[0].CloudTrailEvent' \
--output text | jq '{eventName, errorCode, recipientAccountId}'{
"eventName": "AssumeRoleWithWebIdentity",
"errorCode": null,
"recipientAccountId": "<Workload のアカウント ID>"
}errorCode が null(成功)であり、コントロール有効化中でも sts:AssumeRoleWithWebIdentity は拒否されていない。
9. CloudTrail での確認
ステップ 6 で拒否された sts:AssumeRole が CloudTrail に記録されていることを確認する。sts:AssumeRole は CloudTrail の管理イベントに分類されるため、デフォルトの CloudTrail 設定で記録される。
RCP による拒否イベントは、リソース所有者(Workload アカウント)ではなく、呼び出し元(組織外アカウント)の CloudTrail に記録される。CT.KMS.PV.7 と同じ挙動であり、CT.SECRETSMANAGER.PV.1 では Workload アカウントと組織外アカウントの両方で確認できたのとは対照的である。
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRole \
--start-time <検索開始時刻> \
--end-time <検索終了時刻> \
--query 'Events[].CloudTrailEvent' \
--output text \
--region ap-northeast-1 \
--profile <組織外プロファイル名> | jq 'select(.errorCode != null) | {eventName, errorCode, errorMessage}'{
"eventName": "AssumeRole",
"errorCode": "AccessDenied",
"errorMessage": "User: arn:aws:iam::<組織外アカウント ID>:user/<IAM ユーザー名> is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::<Workload のアカウント ID>:role/ct-sts-pv1-test"
}なお、管理アカウントの Organization Trail(CloudWatch Logs)には、組織外アカウントの拒否イベントは記録されない。Organization Trail は組織内アカウントのイベントのみを集約するためである。
10. CT.STS.PV.1 の無効化
aws controltower disable-control \
--control-identifier arn:aws:controlcatalog:::control/aqnqv7jjgi2dtl6r1v12xglio \
--target-identifier <OU の ARN> \
--profile Master{
"operationIdentifier": "<オペレーション ID>"
}aws controltower get-control-operation \
--operation-identifier <オペレーション ID> \
--query 'controlOperation.{operationType:operationType,status:status,statusMessage:statusMessage}' \
--profile Master{
"operationType": "DISABLE_CONTROL",
"status": "SUCCEEDED",
"statusMessage": "Operation was successful."
}11. 制限解除の確認
コントロール無効化後、組織外プリンシパルからの sts:AssumeRole が再び成功することを確認する。
aws sts assume-role \
--role-arn arn:aws:iam::<Workload のアカウント ID>:role/ct-sts-pv1-test \
--role-session-name external-test-after-disable \
--query 'Credentials.{AccessKeyId:AccessKeyId,Expiration:Expiration}' \
--region ap-northeast-1 \
--profile <組織外プロファイル名>{
"AccessKeyId": "<アクセスキー ID>",
"Expiration": "<有効期限>"
}組織外プリンシパルからのロール引き受けが再び成功した。
12. クリーンアップ
aws iam delete-role \
--role-name ct-sts-pv1-test
(出力なし)