S3バケットポリシーで管理者IAMユーザ以外のアクセスを弾く

![S3BucketPolicy.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/521914/e97ec4ed-1d08-8db2-8842-156226b5ce66.png)

# 背景
- 機密情報が含まれるファイルを格納するS3がある（Protected Bucket）
- Protected Bucketには管理者グループに属するIAMユーザだけがアクセスできるようにしたい（管理者グループ以外のIAMユーザはアクセス不可）

※各IAMグループに属するIAMユーザは下表のような命名規則を想定しています

| ユーザ | グループ |
|--|--|
| 001admin | Admin |
| 002admin | Admin |
| 001deploy | Deploy |
| 002deploy | Deploy |
| 001readonly | ReadOnly |
| 002readonly | ReadOnly |
| 003readonly | ReadOnly |
| 004readonly | ReadOnly |

# IAMポリシーでアクセス制限する
最もシンプルで確実なやり方としては、管理者グループ以外のグループたち（上の例だとDeployグループ、ReadOnlyグループ）に対して、「Protected Bucketに対する明示的なDeny」のポリシーをそれぞれアタッチする方法だと思います。
ただこの方法のデメリットとしては、以下が挙げられます。

- 管理者グループ以外のすべてのIAMグループのポリシーを変更する必要がある（１グループだけ適用するの忘れていた、となりかねない）
- 機密情報を格納したいS3バケットが新たに増えたときに、その都度IAMポリシーの中身を書き換える必要がある

Protected BucketのようなS3バケットの新規作成をCloudFormation等を使ってコード管理したいとき、IAMポリシーの設定だけ手動にするというのは手間に感じます。そこでIAMグループ側ではなく、S3バケット側でアクセス制限をかけられるようにしたい！というのが以降説明する内容の動機になります。

# Protected Bucketのバケットポリシーを設定する

## NotPrincipal要素にワイルドカード(*)は使えない
管理者ユーザ以外のアクセスを拒否したいわけですから、パッと思いつくのは下のような書き方です。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": [
                    "arn:aws:iam::123456789012:root",
                    "arn:aws:iam::123456789012:user/*admin"
                ]
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::protected-bucket/*",
                "arn:aws:s3:::protected-bucket"
            ]
        }
    ]
}
```

[プリンシパル要素と明示的な拒否でワイルドカードを使用するには](https://aws.amazon.com/jp/premiumsupport/knowledge-center/explicit-deny-principal-elements-s3/) にあるように、NotPrincipal要素ではワイルドカードはサポートされていないため、このような記法は認められていません。（Principal要素でもワイルドカード使用不可）

## ではどう書けばいいのか？
Principalとワイルドカードの相性が悪いので、
`"Principal": "*"`と`Condition.StringNotLike`とを組み合わせて表現します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:::protected-bucket/*",
                "arn:aws:s3:::protected-bucket"
            ],
            "Condition": {
                "StringNotLike": {
                    "aws:username": "*admin"
                },
                "StringNotEquals": {
                    "aws:userid": "123456789012"
                }
            }
        }
    ]
}
```

### ①StringNotLike
`"aws:username": "*admin"`とすることで、条件`*admin`にヒットしないIAMユーザに対して明示的にアクセス拒否できます。[^1]

### ②StringNotEquals
`"aws:userid": "123456789012"`
なぜこの部分が必要になるかというと、①だけだとルートアカウントもDenyされてしまうからです。
①と②を並べてあげることで、「"条件`*admin`にヒットしない" かつ "ルートアカウント以外"」すなわち"管理者ユーザ以外の全IAMユーザ"を表現することができます。[^2]


# その他参考になる記事
- [S3バケットへのアクセスを特定IAMユーザにだけ許可して他は弾く](https://qiita.com/sonots/items/bef1ba496d00d9cb7885)
- [[AWS] S3バケットポリシーで、特定のIAMロールだけがバケットにアクセス出来るようにする。](https://qiita.com/n-ishida/items/33cef1d67adc0961625a)
- [特定の IAM ロールのみアクセスできる S3 バケットを実装する際に検討したあれこれ](https://dev.classmethod.jp/articles/s3-bucket-acces-to-a-specific-role/)

[^1]: [文字列条件演算子](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_elements_condition_operators.html#Conditions_String)

[^2]: [複数のキーまたは値を含む条件の評価ロジック](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_multi-value-conditions.html#reference_policies_multiple-conditions-eval) （Conditionブロック内に複数の条件がある場合ANDで評価される）