AWS Control Towerのリージョン拒否コントロールとマネジメントコンソールの罠
この記事は AWS Community Builders Advent Calendar 2023 シリーズ2 の22日目の記事となります。
19日目の記事で米澤さんがService Control Policy(SCP)に悩まされた話をしていました。
※SCP:組織/AWSアカウントレベルでAWS操作を制限できるAWS Organizationsの機能
ティザーサイト構築当初、とても頭を悩ませたポイントがあります。 そう、SCPです。 qiita.com
本記事では今回米澤さんを苦しめたであろうSCPについて分析してみたいと思います。
今日もどこかでSCPに苦しめられている皆様のお役に立てれば幸いです。
米澤さんを苦しめた事象
マネジメントコンソールにおいて、CloudFrontのオリジンを設定するときにAPI Gatewayにデプロイ済みのAPIが選択候補として表示されず設定できない。
結論
タイトルでネタバレしていますが「Control Towerのリージョン拒否コントロール」が原因だったのではないかと推定しています。 また、AWSのマネジメントコンソールの実装自体にも問題がありそうだと考えています。
事象を再現してみる
API Gatewayにて、東京リージョンに scp-test
という適当なAPIをデプロイします。
マネジメントコンソールからCloudFrontディストリビューションを作成します。
ここで事象が再現しました。オリジンとして先ほど構築した scp-test
が選択できるはずなのに表示されません。
画面上には何らエラーは表示されておらず、戸惑うのも無理はありません。
原因を調べてみる
たしかに東京リージョンのAPI GatewayにAPIは正常にデプロイされています。 デプロイされたエンドポイントのURLを直接叩けば、きちんと動作もしています。
とりあえず、Webブラウザの開発者ツールで挙動を見てみると、403エラーが連発しています。
このスクリーンショットで選択している部分では、 eu-west-2
(ロンドン) リージョンのAPI Gatewayのエンドポイントに対して、GET /restapis
を送信しエラーが返ってきています。
GET /restapis
はそのリージョンのAPI GatewayにデプロイされているREST APIをListするAPIです。
GetRestApis - Amazon API Gateway
レスポンスを見てみると、SCPの明示的なDenyにより拒否されていることが分かります。
{ "Message": "User: arn:aws:sts::xxxxxxxxxxxx:assumed-role/AWSReservedSSO_Developer_14b374d3170c6c85/y-kimi is not authorized to perform: apigateway:GET on resource: arn:aws:apigateway:eu-west-2::/restapis with an explicit deny in a service control policy" }
同様のAPI実行を全リージョン分、順に繰り返しているのです。
東京リージョンのエンドポイントへのAPI実行は成功しており、先ほどデプロイしたAPIの情報が正常に返ってきています。
API Gatewayだけでなく、ELBに対しても同様の動きをして403エラーになっていますので、ELBでも事象が再現するのではないかと推測しています。
エラーになった結果、正常に取れている東京リージョンの情報も画面に表示するのは諦めるようです(ここは詳細不明)
SCPを見てみる
同じAPIを実行して、東京リージョンは成功、他のリージョンは失敗となっているので、リージョンを限定しているポリシーが被疑者として挙げられます。
Control Towerには「リージョン拒否コントロール」という統制機能があり、指定リージョン以外のリージョンへのアクセスを禁止することができます(API実行が拒否される)。 地域の準拠法の要件や個人情報の国外移転の観点で国外リージョンの利用を制限したいといったユースケースが考えられます。
リージョン拒否コントロールの設定 - AWS Control Tower
ドキュメントにも以下の通り記載されていますが、リージョン拒否コントロールはAWS組織全体に適用される厄介というか強力なコントロールです。
通常のコントロールはOUを選択して適用しますが、このコントロールは全てのOUに自動的に適用されます。
※OU:AWS OrganizationsにおいてAWSアカウントをグルーピング、階層的に管理するための論理リソース。AWSアカウントを格納するフォルダのようなイメージ
このコントロールはSCPで実装されており、以下のポリシー(2023年12月22日時点)が組織全体に適用されます。
{ "Version": "2012-10-17", "Statement": [ { "Condition": { "StringNotEquals": { "aws:RequestedRegion": [ "us-east-1", "ap-northeast-1" ] }, "ArnNotLike": { "aws:PrincipalARN": [ "arn:aws:iam::*:role/AWSControlTowerExecution" ] } }, "Resource": "*", "Effect": "Deny", "NotAction": [ "a4b:*", "access-analyzer:*", "account:*", "acm:*", "activate:*", "artifact:*", "aws-marketplace-management:*", "aws-marketplace:*", "aws-portal:*", "billing:*", "billingconductor:*", "budgets:*", "ce:*", "chatbot:*", "chime:*", "cloudfront:*", "cloudtrail:LookupEvents", "compute-optimizer:*", "config:*", "consoleapp:*", "consolidatedbilling:*", "cur:*", "datapipeline:GetAccountLimits", "devicefarm:*", "directconnect:*", "ec2:DescribeRegions", "ec2:DescribeTransitGateways", "ec2:DescribeVpnGateways", "ecr-public:*", "fms:*", "freetier:*", "globalaccelerator:*", "health:*", "iam:*", "importexport:*", "invoicing:*", "iq:*", "kms:*", "license-manager:ListReceivedLicenses", "lightsail:Get*", "mobileanalytics:*", "networkmanager:*", "notifications-contacts:*", "notifications:*", "organizations:*", "payments:*", "pricing:*", "quicksight:DescribeAccountSubscription", "resource-explorer-2:*", "route53-recovery-cluster:*", "route53-recovery-control-config:*", "route53-recovery-readiness:*", "route53:*", "route53domains:*", "s3:CreateMultiRegionAccessPoint", "s3:DeleteMultiRegionAccessPoint", "s3:DescribeMultiRegionAccessPointOperation", "s3:GetAccountPublicAccessBlock", "s3:GetBucketLocation", "s3:GetBucketPolicyStatus", "s3:GetBucketPublicAccessBlock", "s3:GetMultiRegionAccessPoint", "s3:GetMultiRegionAccessPointPolicy", "s3:GetMultiRegionAccessPointPolicyStatus", "s3:GetStorageLensConfiguration", "s3:GetStorageLensDashboard", "s3:ListAllMyBuckets", "s3:ListMultiRegionAccessPoints", "s3:ListStorageLensConfigurations", "s3:PutAccountPublicAccessBlock", "s3:PutMultiRegionAccessPointPolicy", "savingsplans:*", "shield:*", "sso:*", "sts:*", "support:*", "supportapp:*", "supportplans:*", "sustainability:*", "tag:GetResources", "tax:*", "trustedadvisor:*", "vendor-insights:ListEntitledSecurityProfiles", "waf-regional:*", "waf:*", "wafv2:*" ], "Sid": "GRREGIONDENY" } ] }
ポリシーの基本的な構造としては、aws:RequestedRegion
で操作対象のリージョンを判定して、指定リージョンでなければ Deny
というルールになっています。
ただし、NotAction
によって、AWSを使う上で必須となる一部の操作は Deny
の対象から除外されています。
API GatewayのAPI一覧を取得する操作は NotAction
には入っていませんので、指定リージョン外では明示的に拒否され、403エラーとなります。
これは上記調査結果とも整合するため、このSCPが原因になっている可能性が高いと推測します。
実際にリージョン拒否コントロールで適用されるSCPだけを対象のアカウントから外すと、問題なくオリジンとしてAPIが表示・選択できました。
回避策
SCPが原因になっているとはいえ、一部のリージョンでエラーになったら正常に取得できたリージョンの情報も表示しないというマネジメントコンソールの実装上の問題もあると思います。 そのため、回避策はマネジメントコンソールを使わないことです。CLIやCDK/CloudFormation等で実装すれば、この問題には当たりません。
前述の通り、リージョン拒否コントロールはAWS組織全体に適用される厄介なコントロールです。 Control Towerからは特定のOUだけ除外ということができず、除外したい場合は裏にいるAWS Organization側から直接SCPをいじって除外してあげる必要があります(Control Tower側でドリフトとして検出されます)。
これは、Control Towerの運用的には好ましくありませんので、やはりマネジメントコンソールの実装をAWSさんに修正いただくのが良さそうです。 フィードバックは出しましたので、AWSさん何卒ご対応よろしくお願いいたします。
まとめ
Control Towerのリージョン拒否コントロールは、AWS組織全体に適用される強力な制御である一方で、このような思わぬトラブルにつながるケースも多く、慎重に利用を検討されるのが良いと思います。 そもそもSCPという仕組み自体が強力であるがゆえに、中々に扱いが難しいものでもありますので、用法用量を守って必要最小限の利用に留めていただくことを推奨します。
以上、Control TowerとSCP、マネジメントコンソールに関する話でした!