🚷

【AWS】Resource Control Policies (RCPs) が追加されました

2024/11/15に公開

はじめに

2024/11/14 に Orgnizations のポリシーとして新たに Resource Control Policies (RCPs) が追加されました。すべてのリージョンで利用可能です。

https://aws.amazon.com/jp/about-aws/whats-new/2024/11/resource-control-policies-restrict-access-aws-resources/

Organizations 内のリソースに予防的コントロールを強制するものになります。例えば、「Organizations のすべての AWS アカウントにおいて、組織外のプリンシパルが S3 バケットにアクセスすることを禁止する」という要件を簡単に満たすことができるようになります。

リリース時点での対象リソースは Amazon S3, STS, KMS, SQS, Secrest Manager です。

前提条件

  • Organizations のすべての機能が有効化されていること(一括請求のみは NG)

注意点

  • Organizations の管理アカウントには適用されない
  • AWS が推奨する RCPs の適用方法は、Root にすぐに適用するのではなく、アカウントレベル→ OU レベル(下位から)→ 適用したいレベル
  • root もしくは OU 単位にアタッチできる RCPs は SCP 同様 5 まで
  • 5120 文字までなので必要に応じてスペースを省く
    • Resource control policies: 5120 characters
  • NotPrincipalNotAction は利用不可
  • (注意点ではないが) *? が Action 要素のどこでも利用可能
    • SCP では ec2:Describe* のように最後に利用するのは可能だったが、 ec2:*Describe のような指定は不可能だった

サンプルポリシー

以下ドキュメントにサンプルポリシーの記載があります。
ドキュメントにも記載がありますが、AWS が推奨するもしくはベストプラクティスの設定という訳ではないので注意してください。

https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_rcps_examples.html

  1. AWS サービス間の混乱した代理問題の予防

リクエストのコンテキストに aws:SourceAccount が存在し、AWS サービスからの呼び出し時に "aws:SourceOrgIDmy-org-id ではない場合に s3, sqs, secrestmanager の全リソースに対するアクションを拒否する。

例えば、CloudTrail の証跡を作成する際に S3 バケットのバケットポリシーで Principal cloudtrail.amazonaws.com を許可すると思います。しかし、AWS アカウントで利用する CloudTrail はすべて上記の Principal になるため、S3 バケットの ARN が分かってしまうと全然関係のない AWS アカウントから S3 バケットに対して CloudTrail ログを出力することができてしまいます。それを防ぐために SourceAccount や SourceOrgID を利用して自分が管理しているアカウントからのみ出力できるようポリシーを設けることが必要になります。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "RCPEnforceConfusedDeputyProtection",
            "Effect": "Deny",
            "Principal": "*",
            "Action": [
                "s3:*",
                "sqs:*",
                "secretsmanager:*"
            ],
            "Resource": "*",
            "Condition": {
                "StringNotEqualsIfExists": {
                    "aws:SourceOrgID": "my-org-id"
                },
                "Bool": {
                    "aws:PrincipalIsAWSService": "true"
                },
                "Null": {
                    "aws:SourceAccount": "false"
                }
            }
        }
    ]
}
  1. HTTPS 接続の強制
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "EnforceSecureTransport",
            "Effect": "Deny",
            "Principal": "*",
            "Action": [
                "sts:*",
                "s3:*",
                "sqs:*",
                "secretsmanager:*",
                "kms:*"
            ],
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:SecureTransport": "false"
                }
            }
        }
    ]
}
  1. TLS バージョンと KMS 暗号化の強制
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "EnforceS3TlsVersion",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": "*",
            "Condition:": {                
                "NumericLessThan": {
                    "s3:TlsVersion": [
                        "1.2"
                    ]
                }
            }
        },
        {
            "Sid": "EnforceKMSEncryption",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "*",
            "Condition": {
                "Null": {
                    "s3:x-amz-server-side-encryption-aws-kms-key-id": "true"
                }
            }
        }
    ]
}

また、Control Tower のコントロールにも 9 種類追加されています。
(実装 = Resource control policy (RCP) で絞り込み)

https://docs.aws.amazon.com/ja_jp/controltower/latest/controlreference/list-of-rcp-controls.html

試してみる

企業の IP アドレスのみ通過させる制御をすべてのバケットに入れる想定で、CloudShell の IP アドレスからのみアクセスを許可する RCPs を適用して挙動を確認してみます。

rcp-test-20241115 というバケットを 33xxxxxxxxxx アカウントに作成し、以下のバケットポリシーを付与します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::36xxxxxxxxxx:role/Admin"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::rcp-test-20241115/*"
        }
    ]
}

36xxxxxxxxxx アカウントから上記バケットのオブジェクトを取得できることを確認します。

$ curl http://checkip.amazonaws.com/
35.75.7.30
$ aws s3api get-object --bucket rcp-test-20241115 --key test test
{
    "AcceptRanges": "bytes",
    "LastModified": "2024-11-15T05:19:38+00:00",
    "ContentLength": 5,
    "ETag": "\"d8e8fca2dc0f896fd7cb4cb0031ba249\"",
    "ContentType": "binary/octet-stream",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}
$ cat test
test

33xxxxxxxxxx アカウントが所属する Organizations の管理アカウントで RCPs を有効化します。

RCPs を有効化すると RCPFullAWSAccess がデフォルトで Root、すべての OU、すべての AWS アカウントにアタッチされます。そうしないとリクエストの許可/拒否の評価ルールによりすべて Deny 扱いされてしまうからですね。

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_evaluation-logic_policy-eval-denyallow.html

33xxxxxxxxxx アカウントは Root -> Sandbox OU に配置しているので、RCPs を確認すると以下のように Root、Sandbox、直接アタッチの計 3 つが表示されています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "*",
            "Resource": "*"
        }
    ]
}

33xxxxxxxxxx アカウントに以下の RCPs を適用します。
(AWS サービスからのアクセスもできなくなるので、実際は PrincipalIsAWSService を利用して除外するなど必要です。)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "*",
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": "52.11.105.11/32"
        }
      }
    }
  ]
}

36xxxxxxxxxx アカウントから再び GetObejct を試すと Access Denied になりました。

$ curl http://checkip.amazonaws.com/
35.75.7.30
$ aws s3api get-object --bucket rcp-test-20241115 --key test test

An error occurred (AccessDenied) when calling the GetObject operation: Access Denied

33xxxxxxxxxx アカウントからの挙動は変わりません。

$ curl http://checkip.amazonaws.com/
52.11.105.11
$ aws s3api get-object --bucket rcp-test-20241115 --key test test
{
    "AcceptRanges": "bytes",
    "LastModified": "2024-11-15T05:19:38+00:00",
    "ContentLength": 5,
    "ETag": "\"d8e8fca2dc0f896fd7cb4cb0031ba249\"",
    "ContentType": "binary/octet-stream",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}
$ cat test
test

ポリシーをデタッチすると 36xxxxxxxxxx のアカウントから再びアクセスできました。

$ curl http://checkip.amazonaws.com/
35.75.7.30
$ aws s3api get-object --bucket rcp-test-20241115 --key test test
{
    "AcceptRanges": "bytes",
    "LastModified": "2024-11-15T05:19:38+00:00",
    "ContentLength": 5,
    "ETag": "\"d8e8fca2dc0f896fd7cb4cb0031ba249\"",
    "ContentType": "binary/octet-stream",
    "ServerSideEncryption": "AES256",
    "Metadata": {}
}

FAQ

Discussion