🛡️

[AWS]海外リージョンの利用統制について実装パターンを考えてみた

2024/05/08に公開

はじめに

実案件でAWSの海外リージョンの利用制限や統制について実装方法を検討する機会があったので、実際に検討した内容を紹介したいと思います。

SCPやControl Towerの機能にも触れていこうと思います。

対象読者

  • AWSのCCoE担当者やOrganizationsの管理者
  • Control Towerの機能を知りたい人

あるあるのお客様要望

私が参画中の金融系のお客様案件で、「東京・大阪以外の海外リージョンの利用を制限したい」という要望をお客様からいただきました。
AWSだと日本国内以外のリージョンを簡単に使えちゃうので、金融系以外でもこの手の要望はあるあるかなと思います。

要望の背景としてはこんなところでした。

  • 個人情報などの機密情報が含まれるデータの所在を日本国内に限定したい
  • 管理外リソースやコストの増加を抑えるために、海外リージョンでのリソース作成を禁止したい

実際開発環境で誤ったリージョンに作ったリソースに気付かず、意図せずコストを発生させてしまった等は普通に起こりがちですよね。

AWSでは特定のリージョンを無効にするようなことはできない

AWSの仕様上、バージニア北部リージョンなど特定のリージョンを使用できないように無効化することはできません。

オプトインリージョンと呼ばれる一部のリージョンは、AWSアカウント上で明示的にアクティベーションしないかぎり使えないですが、それ以外のリージョン(商用リージョンといいます)はアクティベーションや無効化ができません。

この仕様のため、ユーザ側でAWSの機能を活用したり自身で作りこむことで、海外リージョンが使用できない状態(いわば無効化されていると等しい状態)を作り出す必要があります。

いきなり実装パターンのサマリ

上記のような要望から、「予防的統制」または「発見的統制」の観点から海外リージョンの利用を統制することを検討しました。

以下は検討した実装パターンをまとめた表です。どの方法も維持管理上のデメリットがありメンテンナンスや運用が必要なことがわかります。

予防的統制

予防的統制を一言でいうと「使えないようにする方法」です。
実装方法は大きく下記の2つがあります。

なお、Organizationsの利用を前提としています。

- ①Control Towerで「リージョン拒否」を設定 ②海外リージョン利用禁止するSCPを自作
構成図
概要 Control Towerの標準機能「リージョン拒否コントロール」を使用する Control Towerで実装されるSCPを流用し自作する
メリット Control Towerの機能を有効化するだけで実装が完了するため、実装量が他の案と比べて極小 Control Towerのリージョン拒否設定の実装(SCP)をそのまま流用できるため実装量が少ない
デメリット Control Towerの設計・ランディングゾーンの運用が必要。また、既存環境へのContorl Towerの導入はハードルが高いケースがある
例:マルチアカウント構成をすでに自前で取っている場合、そもそもOrganizationsの構成を組んでない場合
自作したSCPのメンテナンスが必要。グローバルサービスが新規追加されるたびにSCPの見直しが必要(詳細は後述)

発見的統制

発見的統制を一言でいうと、「不正を見つけ出す方法」です。予防的統制が不正を未然に防ぐのに対し、発見的統制は既に起きてしまった不正を事後的に発見するための手法といえます。

実装方法は大きく下記の2つがあります。

- ③海外リージョンでのリソース作成・変更操作をCloudTrailとCloudWatchで検知・メール通知 ④海外リージョンのAWS利用料発生をBudgetsで検知・メール通知
構成図
概要 全アカウントのCloudTrailのログ(CloudWatch Logs)を一箇所に集約し、CloudWatchのメトリクスフィルタ・アラームでメール通知する 全アカウントの請求情報をもつOrganizationsの管理アカウントにて、AWS Budgetsを使用し海外リージョンで課金が発生した場合にメール通知する
メリット システムアカウント開発者のAWS利用を一切妨げないため、開発を阻害しない システムアカウント開発者のAWS利用を一切妨げないため、開発を阻害しない
デメリット ・CloudWatch Logsに設定するメトリクスフィルタのメンテナンスが必要。グローバルサービスが追加されるたびにメトリクスフィルタの見直しが必要(詳細は後述)
・操作ユーザや対象AWSアカウント等の情報をメール本文に表示したい場合や、アカウント毎にメール通知先を振り分けたい場合、CloudWatch LogsにサブスクリプションフィルタとLambda等の実装が必要
・Budgetsで予算の計上対象とするリージョンを選択する際に、過去に使用したことないリージョンを設定できない。Budgetsを使う前に全リージョンで何かしらのリソースを作成する必要がある
・通知されるメール本文から誰がいつ何をしたかわからず、メール通知を受け取った際は人手による調査が必要

①~④をどうやって選ぶの?

さて①~④からどのパターンを選ぶのかですが、メリット・デメリットから選ぶのではなく(メリデメをまとめておいてなんですが)、「AWSアカウント全体の構成」(今後の予定を含む)や「要件の強さ」の観点から選ぶしかないと思います。

もし、Control Towerをすでに使用していたり今後使用する予定があれば迷わず①でしょう。

一方、海外リージョンの利用を明確に禁止にしたい場合、発見的統制の③④ではなく、予防的統制の①②を選ぶしかないと思います。
また、予防だけではなく発見的統制による多層防御をかけたい場合、①②に加えて③④を実装するしかないという感じです。

ここからは①~④の実装をさらに深ぼっていこうと思います。

パターン①Control Towerの「リージョン拒否」の設定を有効化

Control Towerは既存のOrganizationsに後でも入れることが出来ますので、Control Towerさえ導入できればこの方法は画面からポチポチするだけで簡単に実装できます。
また、Control Towerを有効化した後に、この設定だけ単独でON/OFFできるのも良い点ですね。

この方式のポイントは以下の通りです。

  • Organizations管理アカウント上のControl Tower設定画面で設定できます。(設定画面は以下の通り)

    • 利用禁止の対象外にするリージョンだけを選択する形です。(以下画像の赤枠部分)
    • 例:国内しか使わないなら、東京リージョンと大阪リージョンを選ぶ
  • Control Towerはバージョンをもち、バージョンをユーザ側でアップデートすることで自動的にSCPが最新化されていきます。(ランディングゾーンとしてバージョンをもっています)

    • 実装されるSCPを後述しますが、このSCPを自前で実装しようとするとAWSのアップデートに合わせてポリシーの中身をユーザ側でメンテンナンスする必要があります。
    • 逆に言えば、バージョン管理さえ適切に行うことで勝手にAWSがメンテンナンスしてくれるのは管理が楽になります。(Control Towerのバージョン管理が楽かという問題がありますが)

参考情報:SCPの中身

この方式でControl Towerによって実装されるSCPを記載しておきます。(ランディングゾーンの2024年5月時点の最新バージョン3.3)

SCPの構成としては、特定リージョンでリージョナルサービス以外のサービスを利用禁止にするため、「NotAction句」にはグローバルサービス(IAM等)を列挙し、「Condition句」にDeny対象外となる特定のリージョンを列挙する形になっています。

なお、このポリシーはIAMポリシーでもそのまま利用できます。

Control TowerのSCP
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Condition": {
        "StringNotEquals": {
          "aws:RequestedRegion": [
            "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"
    }
  ]
}

パターン②海外リージョン利用禁止するSCPを自作

Control Towerを使えないけど海外リージョンの利用を明確に禁止にしたい場合、こちらの方式をとることになると思います。

Organizationsを使用しているなら、Control Towerと全く同じSCPをAWSアカウントを格納したOUにアタッチする形になります。

この方式のポイントは以下の通りです。

  • パターン①並みに実装が簡単ですが、デメリットとしてユーザ側でSCPのメンテナンスが必要です。
    AWSのアップデートでグローバルサービスが登場するたびにSCPのNotAction句を増やしていく必要があります。(詳細は前項のSCPの中身を見てください) それをしないと新しいグローバルサービスが使えない状態になります。

  • もし、使用できるAWSサービスをどこかのホワイトリストで定義し運用しているような場合、この方式はマッチするかなと思います。ホワイトリストの運用と合わせてSCPを見直していくと良いと思います。

  • メリットも書いておくと、Organizationsを使用していない場合でも使用可能な点があげられます。SCPではありませんが、SCPの中身をそのままIAMポリシーとして実装し、IAMグループやIAMロールにアタッチすることでシングルアカウント構成でも同様の制限を実現できます。ただ、ルートユーザに関してはSCPを使わないとどうしてもポリシーがあてられないので、ルートユーザが使用されたことを検知する発見的統制と組み合わせる等の対応が必要になるかもしれません。

パターン③海外リージョンでのリソース作成・変更操作をCloudTrailとCloudWatchで検知・メール通知

CloudTrailのログをCloudWatch Logsに出力し、CloudWatch Logsにメトリクスフィルタ・CloudWatch Alarmを実装することで海外リージョンにリソースが作成・設定変更されたことを検知しメール通知する方式です。

もしOrganizationsを使用している場合、CloudTrailの「組織の証跡」を使用することで、組織内全アカウントのCloudTrailログを管理アカウント上のCloudWatch Logsに集約できるため、アカウントが増えた場合も特に設定変更することなく、同じ仕組みに乗っかることができるのは大きな利点です。

ただし、デメリットとしては下記2点が考えられます。

  • 操作ユーザや操作内容の情報がメール本文に記載されないため、メールを受けた後にユーザ側でログから調査し、操作内容を特定する必要がある。これらの情報をメール表示するには、CloudWatch Logs以降にサブスクリプションフィルタとLambdaの追加実装が必要
  • SCPと同じでグローバルサービスが登場するたびにメトリクスフィルタのフィルタ定義を追加する必要がある(eventSource句にサービスエンドポイント名を列挙)
フィルタ定義の例

{ $.awsRegion != “ap-northeast-1” && $.awsRegion != “ap-northeast-3”
&& $.eventSource != “iam.amazonaws.com ” && $.eventSource != “cloudfront.amazonaws.com ” && $.eventSource != “route53.amazonaws.com ” && $.eventSource != ・・・・ 
&& $.readOnly = 0 }
※ readOnly=0は読み取り以外の操作を意味する

パターン④海外リージョンのAWS利用料発生をBudgetsで検知・メール通知

全アカウントの請求情報をもつ管理アカウントにて、AWS Budgetsを使用し海外リージョンで課金が発生した場合にメール通知する方法です。

具体的な方法は以下の通りです。

  • 管理アカウント上でBudgetsを使用し予算アラームを設定する
  • コスト集計の対象期間を「日別」とし、通知のしきい値を「0 USD」に設定することで海外リージョンでリソース作成されたとき、メール通知できる
  • コスト集計の対象リージョンを選択することができ、東京と大阪以外の全リージョンを選択する。

この方法は、パターン③と同じくアカウントが増えた場合も特に設定変更することなく同じ仕組みに乗っかることができる点にメリットがあります。
また、Organizationsを使用していないシングルアカウントでも同じ方法で実装できることも利点です。

また、デメリットとしては下記2点が考えられます。

  • 通知されるメールからは予算超過したことしかわからず、操作したユーザや操作内容の情報がメール本文に記載されない。また、パターン③のようにLambdaで工夫してもこれらの情報をメールに表示できない。

  • 選択できるリージョンは過去に使用したことがあるリージョンのみであり、未使用リージョンを設定できないという課題があります。(AWSサポートに確認済み)つまり、Payer上で全リージョンで何かしらのリソースを作成する必要がある(下記の図の通り)

2点目に書いた初期設定時が特にめんどくさいですね、、、 
ただし、パターン②③のようにポリシーやフィルタにメンテナンスは発生しないため、初期設定時のめんどくささを乗り越えたら設定値のメンテナンスが不要になるのは大きな利点だと思います。
通知文面から操作したユーザ等を特定できないことを許容できるなら、この方法は意外と使えるのではないでしょうか。

まとめ

以上、海外リージョンの利用制限について実装パターンをまとめてみました。

実装方式はいろいろありますが、組織にあった方式を選んでもらえればと思います。
他にもこんな方式で実装してるよ等あれば、ぜひコメントください!

Discussion