S3ブロックパブリックアクセスって結局どう使いこなすべき？

# 背景

AWS Solution Architect Professional の期限が近くなってきていて、再勉強中です。そんな中、S3のアクセス権限に関して今まで結構あいまいな理解である事に気づきました。一般に公開する時やクロスアカウントでの公開などで重要なポイントです。

自分は資料を読んだだけではナレッジが頭に入らないタイプです。実際にやってみて理解しておこうと思いました。

# 先にまとめ

長くなってしまったので、先に要点まとめです。

- S3の`パブリックアクセス`とは、AWS認証情報がないアクセスの事。インターネット経由かは関係ない。
- 特別な理由がない限り、ブロックパブリックアクセスは有効にしておくべき。
    - AWS認証がない公開アクセスが必要な時でもCloudFrontで対応可能。
- その上で、ACLによる制御は非推奨。アクセスをコントロールしたい時はバケットポリシー使用。
- S3にプライベートアクセス（？）するためには、IAMロールでの許可かバケットポリシーによる許可が必要  
 参考記事：[ポリシーの評価論理](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_evaluation-logic.html)

# 先に感想

同じく長くなったので先に感想です。

今までちゃんと理解できていなかったブロックパブリックアクセスが理解できました。関連してバケットポリシーやOACの役割を明確に把握できた事に加えて、署名付きURLがS3、CloudFrontどちらのURLでも存在する事も確認でき、有意義な検証になりました。

自分と同じようにS3へのアクセス制御に悩んでいる方の参考になれば幸いです。

# 要注意点

バケットポリシーをテストする時があると思います。その際、以下の点注意しましょう。各種設定や操作ができなくなり、rootアカウント操作が必要になります。ちなみにrootアカウントでもポリシーの操作だけ可能でした。

:::note alert
Denyポリシー設定時、PrincipalやActionに "*"を使う時注意！！！Conditionなどで対象指定しましょう！！！
:::

# 動作想定をするため、ドキュメント確認

以下は、ブロックパブリックアクセス機能がリリースされた時の記事と思います。
参考記事：[Amazon S3 Block Public Access – Another Layer of Protection for Your Accounts and Buckets](https://aws.amazon.com/jp/blogs/aws/amazon-s3-block-public-access-another-layer-of-protection-for-your-accounts-and-buckets/)

最初の方に、４つのオプションに関する説明があります。それぞれに関して想定します。

<details><summary>上記記事を読んでの項目別考察</summary><div>

> １．新しいアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする

パブリックアクセス可能なACLがついたオブジェクトを新規追加できなくなるという事ですね。既存は保証するようです。

> ２．任意のアクセスコントロールリスト (ACL) を介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする

上記記事では**overwirte**という単語がありますが、ACL設定自体は変わらず、動作としてパブリックアクセスを拒否するという事のようです。

> ３．新しいパブリックバケットポリシーまたはアクセスポイントポリシーを介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする

新しいパブリックバケットポリシーの使用を禁止するという事に尽きるようです。バケットポリシーを変更する時、より広い範囲に変更しようとすると失敗するという事のようです。

> ４．任意のパブリックバケットポリシーまたはアクセスポイントポリシーを介したバケットとオブジェクトへのパブリックアクセスとクロスアカウントアクセスをブロックする

日本語も英語もどの主語や動詞がどの目的語にかかるのかわかりにくいです。流れからすると、バケットポリシーで公開になっていたとしても無視してブロックすると思われます。

</div></details>

まとめると以下のようになるようです。数値は上から１～４番目を意味します。

|↓対象設定 制限対象動作→|設定変更|設定に基づくパブリックアクセス|
|----|----|----|
|ACL|１|２|
|バケットポリシー|３|４|

# パブリックアクセスって何？

そもそもパブリックアクセスとはどのようなものでしょうか？S3オブジェクトのURLをブラウザでアクセスするのは明らかにパブリックアクセスですが、他のパターンはどうなのでしょうか？以下試してみます。

- オブジェクトURLを使ってブラウザでアクセス
- オンプレマシンからのAPIアクセス
- 他アカウントからのAPIアクセス
- S3 Presigned URLを使ってブラウザアクセス
- 自アカウント内AWSサービスによるアクセス
    - EC2などVPC内サービスからのAPIアクセス
    - CloudShellなどVPC外からのAPIアクセス
    - CloudFrontからS3オリジンへの通常アクセス
    - CloudFrontからS3オリジンへの署名付きURLでアクセス
    - CloudFrontのアクセスログのS3への出力

S3オブジェクトのACLの設定画面にヒントがありました。一言でいうとAWSアカウントの認証情報を持たないアクセスが該当しそうです。これを踏まえて検証していきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/bb4bb620-a9fd-71b1-f711-5637db31bffc.png)


## オブジェクトURLアクセスによる制御確認

オブジェクトURLにアクセスするのは明らかにパブリックアクセスなので、まずはS3の制御設定と動作を確認していきます。バケットポリシーで動作確認します。新しくバケットを作成し、画像ファイルをアップロードします。

### ブロックなし＋バケットポリシーなし

結果：AccessDenied

### ブロックなし＋バケットポリシーあり

ブロックパブリックアクセスを外しただけではアクセスできない様です。以下バケットポリシー追加してみます。

```json:全ユーザーに公開
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::[バケット名]/*"
        }
    ]
}
```

結果：画像が表示されました

ポリシーなしだと暗黙的拒否により拒否されるという事ですね。

### ブロックあり＋バケットポリシーあり

先に説明した、以下のブロックにチェックを付けて有効にします。

> ４．任意のパブリックバケットポリシーまたはアクセスポイントポリシーを介したバケットとオブジェクトへのパブリックアクセスとクロスアカウントアクセスをブロックする

結果：AccessDenied

先の想像通り、ポリシーがあってもブロックされました。

### ブロックあり＋バケットポリシーなし

結果：AccessDenied

当然ですね。アクセスできない要素が重なってます。

### 制御確認まとめ

以上の結果を踏まえると、以下の手順で各アクセスがパブリックアクセスか確認できる事になります。

１．ブロックパブリックアクセスなしにする
２．検証するアクセス方法で、成功するようにバケットポリシーなどを設定
３．ブロックパブリックアクセスありにする
４．２と同様にアクセスして動作確認

## オンプレマシンからのAPIアクセス確認

ブロックなし＋バケットポリシーなしでアクセス可能にしてから、ブロックをつけて再度アクセスしてみます。  
成功しました。インターネット経由のアクセスですが、パブリックアクセス**ではない**という事になります。バケットポリシーがなくても成功したのは、IAMロールの認証でアクセスしているからだと思います。

## 他アカウントからのアクセス確認

CloudShellを使用して、`aws s3 ls s3://バケット名/`の実行が可能でした。
ブロックありでもアクセス可能なので、パブリックアクセス**ではない**ようです。  
**※バケットポリシーで他アカウントの許可をする設定は必要でした。当然他アカウントのIAMによる対象S3バケットへのアクセス許可も必要です。**

## S3 Presigned URLを使ってブラウザアクセス

```bash:Presigned URLを作成
aws s3 presign --expires-in 600 s3://バケット名/image.png
```

出てきたURLで、ブロックなし状態でアクセス可能である事を確認したのち、ブロックありに変更。

```console
https://ブロック名.s3.ap-northeast-1.amazonaws.com/image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***ACCESS_KEY***%2F20231203%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20231203T081905Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=******
```

成功しました。URLにはアクセスキーや署名が入っているので、AWSアカウント認証を持っているのと同等になり、パブリックアクセス**ではない**ようです。

## 自アカウント内AWSサービスによるアクセス

ここまでやっていると、パブリックアクセスが必要なケースがほぼないのではという気がしてきますが、外部ではなく、自アカウント内のAWSサービスからのアクセスを確認していきます。

### EC2などVPC内サービスからのAPIアクセス

AWS資格試験（の各種模擬試験）では、インターネットに出てはダメな場合はVPCエンドポイント経由でS3にアクセスするというのがよく出てきます。今までの実験ではインターネット経由は関係ないはずですが、確認していきます。

`aws s3 ls s3://バケット名/`を実行
予想通り、ブロックの有無にかかわらず成功しました。パブリックアクセス**ではない**ことになります。

### CloudShellなどVPC外からのAPIアクセス

`aws s3 ls s3://バケット名/`を実行
こちらもブロックの有無にかかわらず成功しました。パブリックアクセス**ではない**ことになります。

### CloudFrontからS3オリジンへの通常アクセス

CloudFrontにはIAMロールはつけません。AWSアカウントを持って無い人からのアクセスを受け付けてるのが普通です。CloudFrontのS3オリジンの設定画面にヒントがありました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/f651e539-a836-7243-db34-f8303c74c465.png)

S3へのアクセス方法の指定が可能でした。画像は古いCloudFrontの設定画面なので、OAIを使用していますが、今は上位互換のはずのOACを使うのが推奨です。

CloudFrontにはIAMを付けられるわけではないのでAWSアカウント認証情報はないですね。サービスの性質とも会わないと思います。結果、S3側でのアクセス制限にはバケットポリシーが必要になり、そのためのアクセス元識別子（？）を用意するのがOAIやOACなんだと思います。

#### Publicに指定した場合

まず、Publicアクセスには必要な`"Principal": "*"`のポリシーを追加しておきます。前述「オブジェクトURLアクセスによる制御確認」で使ったポリシーです。

パブリックブロックありにしたら、想像通りAccessDeniedになりました。やはりパブリックアクセス**である**ことになります。パブリックアクセスブロックを外したらアクセスできました。

#### OACを使用した場合

これは各所で検証されていますが、S3側でのパブリックアクセスがブロックされていても、OAC用のバケットポリシーが付与されていればアクセスできます。パブリックアクセス**ではない**ことになります。

### CloudFrontからS3オリジンへの署名付きURLでアクセス

CloudFrontでも署名付きURLでアクセスできるようです。
参考記事：[CloudFront+S3 で署名付き URL を用いたコンテンツアクセスをしてみた 2023](https://dev.classmethod.jp/articles/s3-cloudfront-presigned-url-2023/)

S3 署名付きURLで実験した時はパブリックアクセスではありませんでした。それではCloudFront経由でわざとPublicアクセスにしたとき、署名付きURLでアクセスするとどうなるか試します。

```bash:キー作成部分
$ openssl genrsa -out s3signedurltest.private.pem 2048
$ openssl rsa -pubout -in s3signedurltest.private.pem -out s3signedurltest.pub.pe
$ cat s3signedurltest.pub.pem
-----BEGIN PUBLIC KEY-----
ここに出力された文字列（上下の行を含む）を後述のパブリックキー作成時に登録
-----END PUBLIC KEY-----
```

AWSコンソールにて、CloudFrontカテゴリの左メニュー下部にある「パブリックキー」からパブリックキーを作成します。
同、キーグループを作成します。その時、先ほど作ったパブリックキーを選択して追加しておきます。

CloudFrontディストリビューションのS3オリジンを使用しているビヘイビアで、「ビューワーのアクセスを制限する」を記事の通りに設定します。設定しているキーグループは先ほど作成したキーグループです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/47b99c96-4d45-aa42-7f7c-fdf8aa527243.png)

前述記事ではOACを使用して、それ用のバケットポリシーを設定していました。見たところ、通常のOACにConditionブロックが追加されたものの様です。今回は、先の検証で使ったPublicアクセス用のバケットポリシーにそのConditionブロックを追記しておきます。

```json
            "Condition": {
                "StringEquals": {
                  "AWS:SourceArn": "arn:aws:cloudfront::<AccountID>:distribution/<DistributionID>"
                }
            }
```

この時点では、普通のURLでブラウザでアクセスしようとすると以下のエラーがブラウザに表示されます。

```
<Error>
<Code>MissingKey</Code>
<Message>Missing Key-Pair-Id query parameter or cookie value</Message>
</Error>
```

署名付きURLを生成します。

```bash
$ aws cloudfront sign \
--url https://<DistributionDomain>.cloudfront.net/image.png \
--key-pair-id <先に作成したパブリックキーのID> \
--private-key file://./s3signedurltest.private.pem \
--date-less-than $((`date "+%s"` + 86400))
```

これだけだと`AccessDenied`が出てきました。先ほどPublicアクセス検証の為にバケットポリシーに追加した、Condition部分を削除したら、署名付きURLならアクセスできるようになりました。

そして、パブリックブロックありにしたら、想像通りAccessDeniedになりました。やはりCloudFrontからS3のアクセスをPublicの場合、署名付きURLでもパブリックアクセス**である**ことになります。

CloudFrontからS3署名付きURLはパブリックアクセスとは別軸の設定という事になります。CloudFrontの署名付きURLなので当然検証はCloudFrontで行われ、その後は通常アクセスになるようです。  
CloudFrontからPublicアクセス可能にするには、当然ながらS3をパブリックアクセス可能にしておかなければなりません。その結果、誰でもS3のオブジェクトURLでアクセスできる事になり、セキュリティ的に署名付きURLを使う意味がまったくなくなります。CloudFrontの署名付きURLはOACとセットで使うべきです。

### CloudFrontのアクセスログ出力

CloudFrontのログを出力しようとしたら下記メッセージが。AWS全体方針としてはACL無効が推奨のはずですが、CloudFrontログ出力の為にはACLが必要な様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/866006/00dbf580-8e15-b7f0-e327-d6e2eeea4f73.png)

こちらの記事が参考になりました。
参考記事：[CloudFrontのアクセスログ保存用S3バケットにはACL有効化が必要なので注意しよう](https://dev.classmethod.jp/articles/cloudfront-access-log-dont-choose-no-acl-s3-bucket/)

ACLの有効化は必要でしたが、ブロックパブリックアクセスはありの状態で出力されました。パブリックアクセス**ではない**ようです。
ACLには「外部アカウント」としてのレコードが追加されました。

下記記事を見ると、トレンドしてはACL使わない方向である事は確実と思いますが、CloudFrontのログはまだ未対応という事でしょうか（2023年12月05日現在）。
参考記事：[ACL無効化が推奨されたことでS3のサーバアクセスログ記録のためのアクセス許可はどうなったのか!?確認してみた](https://dev.classmethod.jp/articles/s3-server-access-logging-using-bucket-policy-not-using-acl/)

一般的には、バケットポリシーで`"Principal": {"Service": "logging.s3.amazonaws.com"}`を使って制御するようです。
公式ページ：[ログ配信許可](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/enable-server-access-logging.html#grant-log-delivery-permissions-general)

このようにアクセス権限を分ける必要が出てくるケースもある事から、用途ごとにバケットを分けておくのが基本かと思います。もちろんライフサイクルやバージョニングなど、ほかのポイント的にも用途ごとに分けた方が良いと思います。

### 補足

CloudFront関係の動作確認の時には都度キャッシュクリアが必要でした。


# ポリシー変更ブロック確認

今回はACLに関しては非推奨という事でスルーします。４つめのブロックパブリックアクセスは今までの検証で動作確認できました。残ったもう一つを検証していきます。想定では設定の変更の制御が行われます。

> ３．新しいパブリックバケットポリシーまたはアクセスポイントポリシーを介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする

の動作を確認します。バケットポリシーを空白にし、このブロックをオンにしておきます。

## 公開ポリシー変更

以下ポリシーの、Principal部分だけ変えて、ポリシー変更できるか試します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::[バケット名]",
                "arn:aws:s3:::[バケット名]/*"
            ]
        }
    ]
}
```

- 全公開："Principal": "*"

下記エラー発生。想定通りです。

> バケットポリシーを編集する許可がない、またはバケットポリシーがパブリックアクセスのブロック設定と競合するレベルのパブリックアクセスを付与しています。

- 自アカウント："Principal": {"AWS": "arn:aws:iam::999999999999:root"} 

成功。

- 他アカウント："Principal": {"AWS": "arn:aws:iam::123456789012:root"} 

成功。他アカウントでもAWS認証を持っている場合はパブリックアクセス**ではない**ので制限を受けないようです。


# 用語

## アクセスコントロールリスト

公式ページ：[アクセスコントロールリスト (ACL) の概要](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/acl-overview.html?icmpid=docs_amazons3_console)

今回は細かくは追って行きませんが、オブジェクトやバケットに対して設定できます。オブジェクト自体の読み込みと書き込み権限だけでなく、オブジェクトのACLに対する読み込みや書き込みに対する権限も設定できるようです。

ただ、最近は推奨されないようです。

> Amazon S3 の最新のユースケースの大部分では ACL を使用する必要がなくなっています。オブジェクトごとに個別に制御する必要がある通常ではない状況を除き、ACL は無効にしておくことをお勧めします。

## バケットポリシー

公式ページ：[バケットポリシーの使用](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/bucket-policies.html)

> Amazon S3 バケットとその中のオブジェクトへのアクセス許可を付与できるリソースベースのポリシーです。バケット所有者のみが、ポリシーをバケットに関連付けることができます。バケットに添付された許可は、バケット所有者が所有するバケットのすべてのオブジェクトに適用されます。これらの許可は、他の AWS アカウント が所有するオブジェクトには適用されません。

# 気になる単語

## 「このアカウントのブロックパブリックアクセス設定」

AWSコンソールでS3を選ぶと左側の機能リストに出てきます。バケット毎の設定でなく、アカウントレベルでも設定できるようです。バケット毎の設定で、**新しいバケット**という表現を見た時、”バケット毎の設定なのに新しいバケットに影響？”と思っていましたが、アカウントレベルでの設定と同じ文言リソースを使っているためと思われます。

# 参考にさせて頂いたた記事

[S3のブロックパブリックアクセスを試して理解する](https://zenn.dev/ncdc/articles/ec02e6ce30ff8b)

[[アップデート] オブジェクト所有権でもう悩まない！S3 バケット所有者がアップロード時に自動的にオブジェクト所有権を引き継げるようになりました。](https://dev.classmethod.jp/articles/amazon-s3-object-ownership-enables-bucket-owners-to-automatically-assume-ownership/)