【AWS】バケットポリシー思い通りになってる？

# 本記事の目的

AWSを使っていると必ず付き合っていくことになる**S3**とかいう、古参サービス。
皆さんちゃんと付き合えていますか？
特に、「バケットポリシーが思い通りにならない！！」なんてこと結構あるんじゃないでしょうか？
そんな方向けに、私がバケットポリシーを書く時にどうやって書いているかを紹介したいと思います！
この記事が、どなたかの参考になれば幸いです！

# 本編

## まずは実現したいポリシーを整理しろ！！

バケットポリシーで**最終的に実現したいもの**を、まずは整理しましょう！
以下は一例ですが、私の場合はこんな感じの表を作成します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123830/1c2697d4-a911-3a2b-eb2d-47c551a48b30.png)

エクセル丸出しでお恥ずかしいですが、まぁリアルってことで（笑）
上記の例では、プリンシパル①が管理者権限相当、②が読み取り専用権限相当、③がパワーユーザー相当を想定しています。
※ここで、**プリンシパル**とは、バケットに対してなんらかのアクションを行う人のことを指します。

プリンシパルについては、同じ権限の人ごとにグループ分けしておくといいかと思います。
さて、ここで一つ忘れがちなものがあります。このバケットが存在するアカウントにアクセス権がある人たちです。この人たちのことを忘れてポリシーを書いていると、「Allow」ポリシーで構成されたバケットポリシーになってしまい、意図しないユーザーがダウンロードできてしまったりします。
上記の例では、「Get*」「List*」アクションは全員許可だから明示的に「Deny」ポリシー書かなくていいやと考えてしまうと知らん人がダウンロードして情報漏洩してしまったなんてことになるので、さらにこう書き加えるとわかりやすいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123830/81c7804a-355b-f505-74d7-7cebf27c7eb4.png)

## ポリシーを書いていく！

さてここからポリシーを書いていきましょう！
基本的には「Deny」ポリシーを使っていきます。クロスアカウント目的だったりする場合は「Allow」でもいいと思いますが、こういった複雑なポリシーを書くときは基本的には「Deny」ポリシーで穴あけしていくのが定石だと思います。
※Allowで許可されていても、Denyで拒否が一つでも入っていたらアクセスできなくなるため。もちろんクロスアカウントの場合は別途Allowステートメントも書きましょう！

ポリシーのステートメントはは権限範囲が同じアクションごとに分けて書いていきます。
今回の例では以下の三つのステートメントを書きます。

- ステートメント①
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123830/9e1eedd4-fc6a-1111-16b7-3e77a8d6c6c9.png)

- ステートメント②
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123830/ebd051b7-c6f9-9232-94a1-324215b7068d.png)

- ステートメント③
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123830/412fa8a5-dd85-10d2-0cf2-f91e04333539.png)

### ステートメント①

許可するプリンシパルがIAMユーザーなどの場合は以下のように記述することができます。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ReadOnly",
            "Effect": "Deny",
            "Action": [
                "s3:List*",
                "s3:Get*"
            ],
            "NotPrincipal": {
                "AWS": [
                    "プリンシパル①",
                    "プリンシパル②",
                    "プリンシパル③"
                ]
            },
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}
```

ここでその他のユーザーからアクセスが「List*」と「Get*」ができないことを確認しておきましょう！
単体テストと結合テストをちゃんとやりましょうね！

ここでステートメントごとに表を更新していくようにしておくと、よいかと思います。
今の段階でのポリシーの権限一覧表を作っておくとどこかでおかしくなった時に振り返りやすいです！

- ステートメント①のみのポリシー権限表

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123830/db4afc5b-4f7d-9ef3-3592-d29f5b597bf4.png)

### ステートメント②

まずはこちらも単体で作ってテストしてみましょう。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PowerUser",
            "Effect": "Deny",
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "NotPrincipal": {
                "AWS": [
                    "プリンシパル①",
                    "プリンシパル③"
                ]
            },
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}
```

単体が終わったらステートメント①とつなげてステートメント①のポリシーを侵害していないかチェックします。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ReadOnly",
            "Effect": "Deny",
            "Action": [
                "s3:List*",
                "s3:Get*"
            ],
            "NotPrincipal": {
                "AWS": "arn:aws:iam::アカウントID:user/IAMユーザー名"
            },
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        },
        {
            "Sid": "PowerUser",
            "Effect": "Deny",
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "NotPrincipal": {
                "AWS": [
                    "プリンシパル①",
                    "プリンシパル③"
                ]
            },
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}
```

ここでもこの段階での表を作っておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123830/0141aaff-bbc2-daca-183b-e3ce5a81be99.png)

### ステートメント③

はい、あとは反復です！

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Administrator",
            "Effect": "Deny",
            "NotAction": [
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:List*",
                "s3:Get*"
            ],
            "NotPrincipal": {
                "AWS": [
                    "プリンシパル①"
                ]
            },
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}
```

Notがいくつもあるとややこしいですね（笑）
そういうときは日本語に直してあげると整理できます。
上記の例だったら

**プリンシパル①以外**のすべての人が**「s3:PutObject、s3:DeleteObject、s3:List*、s3:Get*」以外**のアクションを実行することを**拒否**する

みたいな感じです。

最後に全部くっつけてテストしましょう！

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ReadOnly",
            "Effect": "Deny",
            "Action": [
                "s3:List*",
                "s3:Get*"
            ],
            "NotPrincipal": {
                "AWS": "arn:aws:iam::アカウントID:user/IAMユーザー名"
            },
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        },
        {
            "Sid": "PowerUser",
            "Effect": "Deny",
            "Action": [
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "NotPrincipal": {
                "AWS": [
                    "プリンシパル①",
                    "プリンシパル③"
                ]
            },
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        },
        {
            "Sid": "Administrator",
            "Effect": "Deny",
            "NotAction": [
                "s3:PutObject",
                "s3:DeleteObject",
                "s3:List*",
                "s3:Get*"
            ],
            "NotPrincipal": {
                "AWS": [
                    "プリンシパル①"
                ]
            },
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}
```

最後に表を作成して最初に作成した表と一致するかを確認しましょう！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1123830/6c562e8c-c4de-da90-0e19-a50d6ad8edde.png)

## Appendix

### IAM ICユーザーを使用している場合

さきほどまで紹介していたポリシーはプリンシパルがIAMユーザーの場合を想定していました。
最近はIAM Identity Centerを使用することの方が多いかと思いますので、その場合のポリシーの例（ステートメント①）も載せておきます。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ReadOnly",
            "Effect": "Deny",
            "Action": [
                "s3:List*",
                "s3:Get*"
            ],
            "Principal": "*",
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:userId": [
                        "*:Identity Centerのユーザー名"
                    ]
                }
            }
        }
    ]
}
```

# 最後に

今回は私がバケットポリシーを書く時にどうやって書いているかを紹介してみました！
あくまで「私はこうやっているよ」という紹介でしたが、どなたかの参考になれば幸いです！

PS.Denyポリシー適用するとき、ミスるとバケットポリシー編集できなくなるので気をつけましょう！（やらかした。。。）