基本的な使い方

--bucketにバケット名を指定する。

$ aws s3api get-bucket-policy --bucket <bucket-name>

実行例

$ aws s3api get-bucket-policy --bucket example-bucket
{
    "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"cloudfront.amazonaws.com\"},\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::example-bucket/*\",\"Condition\":{\"StringEquals\":{\"aws:SourceArn\":\"arn:aws:cloudfront::123456789012:distribution/E1EXAMPLE123ABC\"}}}]}"
}

Policyの値はJSONオブジェクトではなく、エスケープされた文字列である。
そのままでは読みづらいため、次のように整形する。

整形して表示する

--query Policy --output textでポリシー本体の文字列だけを取り出し、jqに渡す。

$ aws s3api get-bucket-policy --bucket example-bucket --query Policy --output text | jq .
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::example-bucket/*",
      "Condition": {
        "StringEquals": {
          "aws:SourceArn": "arn:aws:cloudfront::123456789012:distribution/E1EXAMPLE123ABC"
        }
      }
    }
  ]
}

エスケープはJSON出力時に付くだけであり、--output textでは付かない。
get-bucket-policyのレスポンスはPolicyのみのため--queryを省いても同じ結果になるが、複数のフィールドを返すコマンドではタブ区切りで並ぶため、取り出したいフィールドを--queryで指定する習慣にしておくとよい。

jqを使わない場合はPythonでも整形できる。

$ aws s3api get-bucket-policy --bucket example-bucket --query Policy --output text | python3 -m json.tool

ポリシーが設定されていない場合

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

$ aws s3api get-bucket-policy --bucket example-no-policy-bucket
aws: [ERROR]: An error occurred (NoSuchBucketPolicy) when calling the GetBucketPolicy operation: The bucket policy does not exist

空のポリシーが返るのではなくエラーで終了するため、スクリプトで扱う際はエラーを前提にした分岐が必要である。

バケット自体が存在しない場合はNoSuchBucketになる。

aws: [ERROR]: An error occurred (NoSuchBucket) when calling the GetBucketPolicy operation: The specified bucket does not exist

バケット名が正しくても、他アカウントが所有するバケットに対してはAccessDeniedが返る。
S3はバケットの存在自体を秘匿するため、エラーコードだけでは名前の誤りか権限不足かを判別できない。

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

aws s3api list-bucketsと組み合わせると、ポリシーが設定されているバケットだけを一覧できる。

$ for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
    policy=$(aws s3api get-bucket-policy --bucket "$bucket" --query Policy --output text 2>/dev/null)
    if [ -n "$policy" ]; then
      echo "=== $bucket"
      echo "$policy" | jq -c '[.Statement] | flatten | .[] | {Effect, Principal, Action}'
    fi
done
=== example-bucket
{"Effect":"Allow","Principal":{"Service":"cloudfront.amazonaws.com"},"Action":"s3:GetObject"}
=== example-deploy-bucket
{"Effect":"Deny","Principal":"*","Action":"s3:*"}
=== example-public-bucket
{"Effect":"Allow","Principal":"*","Action":"s3:GetObject"}

ポリシーのないバケットは2>/dev/nullでエラーを捨て、変数が空かどうかで判定する。
ただし2>/dev/nullは権限不足のAccessDeniedも同時に捨てるため、読めなかったバケットもポリシーなしとして扱われる。監査目的で棚卸しする場合はエラー内容も出力する。

StatementはIAMポリシーの文法上、配列と単一オブジェクトのどちらも書ける。
[.Statement] | flattenで配列に揃えておくと、どちらの形式でも同じように処理できる。

Principal*のAllowを含むバケットは意図せず公開している可能性があるため、優先して中身を確認する。
なおs3:*をDenyしているポリシーでも、aws:SecureTransportfalseのときだけHTTPアクセスを拒否する条件付きのDenyは多い。
Effectだけで判断せず、Conditionまで含めて読む。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::example-deploy-bucket/*",
        "arn:aws:s3:::example-deploy-bucket"
      ],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}

ファイルに保存する

取り出したポリシーはそのままJSONファイルとして保存できる。

$ aws s3api get-bucket-policy --bucket example-bucket --query Policy --output text | jq . > bucket-policy.json

保存したファイルはput-bucket-policyの入力にそのまま使えるため、変更前のバックアップとして残しておくと復旧しやすい。

$ aws s3api put-bucket-policy --bucket example-bucket --policy file://bucket-policy.json

put-bucket-policyはポリシー全体を置き換える。差分をマージするのではないため、取得したJSONを編集してから渡す。
ポリシーごと削除する場合はaws s3api delete-bucket-policy --bucket example-bucketを使う。

Gitで管理しておけば、ポリシーの変更差分も追跡できる。

バケットの取り違えを防ぐ

--expected-bucket-ownerにアカウントIDを指定すると、所有者が一致しない場合にリクエストが失敗する。

$ aws s3api get-bucket-policy --bucket example-bucket --expected-bucket-owner 123456789012

所有者が異なる場合はAccessDeniedが返る。

aws: [ERROR]: An error occurred (AccessDenied) when calling the GetBucketPolicy operation: Access Denied

複数アカウントを扱う場合、プロファイルの指定ミスに気付きやすくなる。

必要な権限

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

コマンド必要なアクション
get-bucket-policys3:GetBucketPolicy
list-bucketss3:ListAllMyBuckets

権限が不足している場合はAccessDeniedが返る。
ReadOnlyAccessAmazonS3ReadOnlyAccessにはどちらのアクションも含まれるため、閲覧目的であればマネージドポリシーで足りる。

参考