2026年6月2日

2026年6月2日

AWS S3のアクセス制御とバケットポリシー|WordPressで使うバケットを安全に公開・制限する書き方

S3 のアクセス制御は、バケットポリシーだけで決まるわけではありません。「パブリックアクセスのブロック」「オブジェクト所有者(ACL の扱い)」「IAM ポリシー」「バケットポリシー」が重なって判定され、どれか1つに明示的な拒否があればアクセスは通りません。この記事では、WordPress のメディア配信やバックアップ保存に使うバケットを例に、それぞれの層の役割、目的別のバケットポリシーの書き方、403(AccessDenied)が出たときの調べ方までを説明します。読み終えると、「公開したいものだけを、決めた経路からだけ読ませる」設定を自分で組み立てられるようになります。

S3 の既定値やコンソールの表記は更新されることがあります。ここでは AWS の公開ドキュメント(2026年10月現在)に沿って説明しますが、作業前に公式ドキュメントでも確認し、本番バケットを変更する前に現在のポリシーを保存しておいてください。

アクセスを決める4つの層

S3 へのリクエストは、おおむね次の層を通って許可・拒否が決まります。

  1. パブリックアクセスのブロック:アカウント単位とバケット単位で設定できる安全装置です。有効にすると、誰にでも読ませるようなバケットポリシーや ACL を付けようとしても拒否されるか、無視されます。
  2. オブジェクト所有者(Object Ownership):ACL を使うかどうかを決めます。「バケット所有者の強制」にすると ACL は無効になり、アクセス制御はポリシーだけで行います。
  3. IAM ポリシー(アイデンティティベース):IAM ユーザーやロールに付けて、「この人(このサーバー)が何をできるか」を決めます。
  4. バケットポリシー(リソースベース):バケットに付けて、「このバケットに誰が何をできるか」を決めます。別アカウントや CloudFront などの AWS サービスに許可を出すときはこちらを使います。

新しく作るバケットは、パブリックアクセスのブロックが有効で、ACL も無効な状態が既定になっています。以前の解説記事にある「ファイルごとに ACL で公開する」手順は、この既定のままでは通りません。現在は、ACL は無効のまま、ポリシーで必要な経路だけを開ける考え方が基本です。

判定のルール

同じアカウント内のアクセスでは、IAM ポリシーかバケットポリシーのどちらかで許可されていれば通ります。別アカウントからのアクセスでは、相手側の IAM ポリシーと、こちらのバケットポリシーの両方で許可が必要です。そして、どこか1か所でも "Effect": "Deny" に当てはまれば、ほかでいくら許可していても拒否されます。

もう1つ見落としやすいのが暗号化です。オブジェクトが SSE-KMS(KMS キーによる暗号化)で保存されている場合、S3 の権限に加えて KMS キーの kms:Decrypt などの権限も必要です。S3 の権限は合っているのに読めない、という場合はキーポリシーも確認します。

まず現状を確認する

設定を変える前に、今の状態を CLI で書き出しておきます。your-bucket-name は自分のバケット名に置き換えてください。

# パブリックアクセスのブロック
aws s3api get-public-access-block --bucket your-bucket-name

# オブジェクト所有者(ACL の有効・無効)
aws s3api get-bucket-ownership-controls --bucket your-bucket-name

# 現在のバケットポリシー(無い場合はエラーになる)
aws s3api get-bucket-policy --bucket your-bucket-name \
  --query Policy --output text > policy-backup.json

policy-backup.json は、変更後に問題が起きたときに戻すための控えです。オブジェクト所有者が BucketOwnerEnforced なら ACL は無効、パブリックアクセスのブロックの4項目がすべて true なら、ポリシーで公開しようとしても止められる状態です。

目的別のバケットポリシー

ポリシーの Resource には、バケットそのものを表す ARN(arn:aws:s3:::your-bucket-name)と、中のオブジェクトを表す ARN(arn:aws:s3:::your-bucket-name/*)の2種類があります。s3:ListBucket はバケットに、s3:GetObject や s3:PutObject はオブジェクトに対する操作なので、ARN を取り違えると「書いたのに効かない」状態になります。いちばん多い書き間違いなので、先に覚えておくと調査が早くなります。

CloudFront からだけ読ませる(メディア配信)

WordPress のメディアを S3 に置いて配信するなら、バケットは非公開のままにして CloudFront のオリジンアクセスコントロール(OAC)経由で読ませる構成が安全です。パブリックアクセスのブロックも有効のままで動きます。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowCloudFrontOACRead",
      "Effect": "Allow",
      "Principal": { "Service": "cloudfront.amazonaws.com" },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*",
      "Condition": {
        "StringEquals": {
          "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/EXAMPLEDISTID"
        }
      }
    }
  ]
}

Condition の AWS:SourceArn で、自分のディストリビューションからのリクエストだけに絞っています。これを省くと、ほかのアカウントの CloudFront からも読めてしまうおそれがあるため、削らないでください。アカウント ID とディストリビューション ID は例なので、自分の値に置き換えます。CloudFront のコンソールで OAC を設定すると、この形の雛形をコピーできることもあります。

HTTPS 以外の通信を拒否する

どの用途のバケットにも足しておきたいのが、暗号化されていない通信を拒否する文です。

{
  "Sid": "DenyInsecureTransport",
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": [
    "arn:aws:s3:::your-bucket-name",
    "arn:aws:s3:::your-bucket-name/*"
  ],
  "Condition": {
    "Bool": { "aws:SecureTransport": "false" }
  }
}

これは上の OAC の文と同じ Statement 配列に並べて使います。AWS CLI や SDK、CloudFront からの通信は通常 HTTPS なので影響はありませんが、古いツールや自作スクリプトが HTTP で接続していると止まるので、適用後に各処理の動作を確認してください。

WordPress のバックアップ保存先として使う

バックアップ用のプラグインやスクリプトに権限を渡すときは、バケットポリシーよりも、WordPress サーバーの IAM ロール(EC2 ならインスタンスプロファイル)に付ける IAM ポリシーで絞るほうが管理しやすくなります。次は wp-backups/ 以下だけを読み書きできるようにする例です。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListBackupPrefix",
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::your-bucket-name",
      "Condition": {
        "StringLike": { "s3:prefix": ["wp-backups/*"] }
      }
    },
    {
      "Sid": "ReadWriteBackupObjects",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject"],
      "Resource": "arn:aws:s3:::your-bucket-name/wp-backups/*"
    }
  ]
}

古い世代を自動で消す機能を使うプラグインなら s3:DeleteObject も必要になりますが、サーバーが乗っ取られたときにバックアップまで消される危険が増えます。削除は S3 のライフサイクルルールに任せ、サーバーには削除権限を渡さない構成も検討してください。必要な権限はプラグインごとに違うので、プラグインのドキュメントで確認し、足りないものだけを追加します。アクセスキーを使う場合も、キーを wp-config.php に書くならファイルの権限を絞り、Git などに含めないようにします。

ポリシーを適用する

作ったポリシーは、適用前に IAM Access Analyzer で検証できます。書式の誤りや、意図より広い許可になっていないかの警告が出ます。

aws accessanalyzer validate-policy \
  --policy-document file://policy.json \
  --policy-type RESOURCE_POLICY \
  --validate-policy-resource-type AWS::S3::Bucket

aws s3api put-bucket-policy --bucket your-bucket-name --policy file://policy.json

put-bucket-policy は既存のポリシーを丸ごと置き換えます。追記のつもりで一部だけを送ると、それまでの許可が消えるので、現在のポリシー全体に文を足したファイルを送ってください。

Deny を含むポリシーは、書き方によっては自分自身も締め出します。たとえば特定の VPC エンドポイント以外を拒否する文を入れると、コンソールや手元の PC からも操作できなくなります。締め出されたときは、アカウントのルートユーザーでバケットポリシーを削除して戻せますが、ルートユーザーを使う手順は手間がかかるので、強い Deny はステージング用のバケットで試してから本番に入れるのが無難です。

403(AccessDenied)が出たときの調べ方

同じ 403 でも、原因の層によって直す場所が変わります。可能性の高い順に確認します。

  1. Resource の ARN の取り違え:一覧取得だけ失敗するならバケット ARN、読み書きだけ失敗するなら /* 付きの ARN を見直します。
  2. ACL を付けてアップロードしている:ACL が無効なバケットに、public-read などの ACL 付きでアップロードすると失敗します。エラーコードが AccessControlListNotSupported なら、プラグインや CLI の設定で ACL を付けない(またはバケット所有者の強制に対応する)設定に変えます。
  3. パブリックアクセスのブロック:誰でも読める前提の設定(Principal: "*" の許可など)は、ブロックが有効だと効きません。公開を止めるか、CloudFront 経由に切り替えます。
  4. 明示的な拒否:HTTPS 必須の文や、組織の SCP(AWS Organizations のポリシー)による拒否に当たっていないか確認します。
  5. KMS キーの権限:SSE-KMS で暗号化されたオブジェクトなら、キーポリシーと IAM 側の KMS 権限を確認します。
  6. CloudFront の設定:CloudFront 経由だけ 403 になる場合は、オリジンに OAC が付いているか、ポリシーのディストリビューション ARN が合っているかを見ます。存在しないキーを要求しても、s3:ListBucket の許可が無いと 404 ではなく 403 が返るため、パスの誤りも疑ってください。

CloudTrail を有効にしていれば、拒否されたリクエストの呼び出し元やエラー内容をイベント履歴から確認できます。原因が分からないときは、まず「誰が」「どの ARN に」「何の操作を」しようとしたかを特定すると、どの層を見ればよいかが絞れます。

まとめ

  • S3 のアクセスは、パブリックアクセスのブロック・オブジェクト所有者・IAM ポリシー・バケットポリシーの組み合わせで決まり、どこかに明示的な拒否があれば通らない。
  • 新しいバケットはパブリックアクセスのブロックが有効で ACL も無効が既定。公開はポリシーと CloudFront の OAC で行う。
  • バケット ARN とオブジェクト ARN(/*)を正しく使い分ける。
  • バックアップ用の権限は IAM ロールで接頭辞単位に絞り、削除権限は必要な場合だけ渡す。
  • 適用前に現行ポリシーを保存し、Access Analyzer で検証してから置き換える。403 は層ごとに順に切り分ける。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲