バケットポリシーによる公開を確認する

aws s3api get-bucket-policy-statusは、バケットポリシーの結果としてバケットがパブリック扱いになっているかを返す。

$ aws s3api get-bucket-policy-status --bucket example-public-bucket
{
    "PolicyStatus": {
        "IsPublic": true
    }
}

ポリシーの中身を読まずに判定できるため、棚卸しの一次判定に向いている。

公開していないバケットではfalseを返す。

$ aws s3api get-bucket-policy-status --bucket example-bucket
{
    "PolicyStatus": {
        "IsPublic": false
    }
}

バケットポリシーを設定していないバケットではNoSuchBucketPolicyのエラーになる。

aws: [ERROR]: An error occurred (NoSuchBucketPolicy) when calling the GetBucketPolicyStatus operation: The bucket policy does not exist

S3はまずポリシーをパブリックとみなし、非パブリックと判定できるかを評価する。
Principalをアカウントやロールに限定するか、aws:SourceArnaws:SourceVpceaws:PrincipalOrgIDなどの条件キーで固定値を指定していればfalseになる。
条件キーを使っていてもvpc-*のようにワイルドカードを含む値では限定したとみなされず、trueのままである。

ただしIsPublicはバケットポリシーだけを見た判定である。
ACLによる公開は含まれず、ブロックパブリックアクセスで実際のアクセスが遮断されている状態も反映されない。
IsPublictrueでも直ちに公開されているとは限らず、逆にfalseでもACL経由で公開されている可能性がある。

ポリシーの中身を読む方法は【AWS CLI】aws s3api get-bucket-policyでS3のバケットポリシーを確認する を参照。

ブロックパブリックアクセスの設定を確認する

バケット単位のブロックパブリックアクセスはget-public-access-blockで確認する。

$ aws s3api get-public-access-block --bucket example-bucket
{
    "PublicAccessBlockConfiguration": {
        "BlockPublicAcls": true,
        "IgnorePublicAcls": true,
        "BlockPublicPolicy": true,
        "RestrictPublicBuckets": true
    }
}

4つの設定はそれぞれ役割が異なる。

設定内容
BlockPublicAclsパブリックなACLの新規設定を拒否する。設定済みのACLには影響しない
IgnorePublicAclsバケットとバケット内のオブジェクトのパブリックなACLをすべて無視する。ACLを新しく設定する操作は拒否しない
BlockPublicPolicyパブリックなバケットポリシーの新規設定を拒否する。設定済みのポリシーには影響しない
RestrictPublicBucketsパブリックなポリシーを持つバケットへのアクセスを、AWSサービスプリンシパルとバケット所有者アカウント内の認可済みユーザーに限定する

BlockPublicAclsBlockPublicPolicyは新規設定を拒否するだけであり、設定済みのポリシーやACLは残る。
既存の公開状態を実際に遮断するのはIgnorePublicAclsRestrictPublicBucketsである。
IsPublictrueのバケットを見つけたら、RestrictPublicBucketsもあわせて確認する。

RestrictPublicBucketsが有効な場合、AWSサービスプリンシパルを除くクロスアカウントアクセスはすべて遮断される。
ポリシー内に1文でもパブリックな許可があるとポリシー全体がパブリック扱いになるため、明示的に許可した他アカウントからのアクセスも同時に遮断される。

ブロックパブリックアクセスは設定済みのポリシーやACLを書き換えない。
設定を外せば再びパブリックになるため、公開状態を解消するにはポリシーやACL自体を修正する。

バケット単位の設定が未設定の場合はNoSuchPublicAccessBlockConfigurationのエラーになる。

aws: [ERROR]: An error occurred (NoSuchPublicAccessBlockConfiguration) when calling the GetPublicAccessBlock operation: The public access block configuration was not found

アカウント単位の設定を確認する

ブロックパブリックアクセスはアカウント単位でも設定できる。
アカウント単位の設定はs3controlのサブコマンドで確認する。

$ aws s3control get-public-access-block --account-id 123456789012
{
    "PublicAccessBlockConfiguration": {
        "BlockPublicAcls": true,
        "IgnorePublicAcls": true,
        "BlockPublicPolicy": true,
        "RestrictPublicBuckets": true
    }
}

アカウントIDはaws sts get-caller-identityで取得できるため、次のようにつなげられる。

$ aws s3control get-public-access-block --account-id $(aws sts get-caller-identity --query Account --output text)

アカウント単位でも未設定の場合はNoSuchPublicAccessBlockConfigurationのエラーになる。

バケット単位とアカウント単位で設定が異なる場合、S3は最も制限の強い組み合わせを適用する。
バケット単位がfalseでも、アカウント単位がtrueであればパブリックアクセスは遮断される。
バケット単位の設定だけを見て公開されていると判断せず、アカウント単位の設定もあわせて確認する。

ACLによる公開を確認する

バケットポリシーとは別に、ACLでも公開できる。
get-bucket-aclで許可の一覧を取得する。

$ aws s3api get-bucket-acl --bucket example-bucket
{
    "Owner": {
        "ID": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
    },
    "Grants": [
        {
            "Grantee": {
                "ID": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2",
                "Type": "CanonicalUser"
            },
            "Permission": "FULL_CONTROL"
        }
    ]
}

Grantee.TypeCanonicalUserで所有者のみであれば、パブリック公開はされていない。
所有者以外のCanonicalUserがあれば、他アカウントへの共有である。
パブリック公開されている場合はGroupの許可が現れる。

{
    "Grantee": {
        "Type": "Group",
        "URI": "http://acs.amazonaws.com/groups/global/AllUsers"
    },
    "Permission": "READ"
}
URI対象
http://acs.amazonaws.com/groups/global/AllUsers認証の有無を問わず誰でもアクセスできる
http://acs.amazonaws.com/groups/global/AuthenticatedUsers署名付きリクエストであれば、AWSアカウントを持つ全員がアクセスできる

AuthenticatedUsersは自分のアカウント内のユーザーではなく、AWSアカウントを持つ任意のユーザーを指す。
実質的に公開と変わらないため、AllUsersと同じ扱いで確認する。

--queryで公開対象の許可だけを抽出できる。

$ aws s3api get-bucket-acl --bucket example-bucket \
    --query "Grants[?Grantee.URI!=null && (contains(Grantee.URI, 'AllUsers') || contains(Grantee.URI, 'AuthenticatedUsers'))]"

該当がなければ空の配列を返す。

[]

ただしget-bucket-aclが返すのは、保存されたACLそのものではなくブロックパブリックアクセス適用後の有効な権限である。
IgnorePublicAclsが有効なバケットでは、パブリックなACLが残っていても結果には現れない。
ブロックパブリックアクセスを外すと再びパブリックになるため、ACL自体を棚卸しする場合はブロックパブリックアクセスの設定とあわせて判断する。

なお、ACLを無効化しているバケットではACLによる公開は起こらない。
get-bucket-ownership-controlsで確認する。

$ aws s3api get-bucket-ownership-controls --bucket example-bucket
{
    "OwnershipControls": {
        "Rules": [
            {
                "ObjectOwnership": "BucketOwnerEnforced"
            }
        ]
    }
}

BucketOwnerEnforcedであればACLは無効であり、バケットポリシーだけを確認すればよい。
2023年4月以降に作成したバケットは、ObjectOwnershipを明示していなければBucketOwnerEnforcedになる。
同じ時期からブロックパブリックアクセスの4設定も新規バケットでデフォルト有効になった。既存のバケットの設定は変更されない。

Object Ownershipを一度も設定していないバケットではOwnershipControlsNotFoundErrorのエラーになる。

aws: [ERROR]: An error occurred (OwnershipControlsNotFoundError) when calling the GetBucketOwnershipControls operation: The bucket ownership controls were not found

エラーになるバケットはACLが有効であるため、ACLの確認がとくに必要な対象である。

すべてのバケットをまとめて確認する

list-bucketsと組み合わせると、パブリック公開されているバケットだけを抽出できる。

$ for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
    is_public=$(aws s3api get-bucket-policy-status --bucket "$bucket" --query PolicyStatus.IsPublic --output text 2>/dev/null)
    if [ "$is_public" = "True" ]; then
      echo "$bucket"
    fi
done
example-public-bucket

--output textはブール値をTrueFalseで出力する。
JSONのtrueとは先頭の文字が異なるため、比較する際は注意する。

公開状態とブロックパブリックアクセスの設定を並べて出力すると、対応の優先順位を判断しやすい。

$ for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
    is_public=$(aws s3api get-bucket-policy-status --bucket "$bucket" --query PolicyStatus.IsPublic --output text 2>/dev/null)
    restrict=$(aws s3api get-public-access-block --bucket "$bucket" --query PublicAccessBlockConfiguration.RestrictPublicBuckets --output text 2>/dev/null)
    printf "%-40s policy=%-5s restrict=%s\n" "$bucket" "${is_public:--}" "${restrict:--}"
done
example-assets-bucket                    policy=-     restrict=True
example-backup-bucket                    policy=-     restrict=-
example-bucket                           policy=False restrict=True
example-logs-bucket                      policy=False restrict=-
example-public-bucket                    policy=True  restrict=False

-は設定自体が存在しない状態を表す。
policy=Trueかつrestrict=Falseのバケットが実際に公開されているため、優先して確認する。

2>/dev/nullは権限不足のAccessDeniedも捨てるため、読めなかったバケットも-として並ぶ。
すべてのバケットを確実に棚卸しする場合はエラー内容も出力する。

一覧に挙がったバケットのうち、get-bucket-ownership-controlsBucketOwnerEnforcedを返さないバケットはACLも確認する。
継続的に監視する場合は、IAM Access Analyzer for S3でパブリックアクセスを検出する方法もある。

必要な権限

コマンドの実行にはそれぞれ次の権限が必要である。

コマンド必要なアクション
list-bucketss3:ListAllMyBuckets
get-bucket-policy-statuss3:GetBucketPolicyStatus
get-public-access-blocks3:GetBucketPublicAccessBlock
s3control get-public-access-blocks3:GetAccountPublicAccessBlock
get-bucket-acls3:GetBucketAcl
get-bucket-ownership-controlss3:GetBucketOwnershipControls

ReadOnlyAccessAmazonS3ReadOnlyAccessにはいずれのアクションも含まれるため、棚卸し目的であればマネージドポリシーで足りる。

参考