ゾンビ化したバケットを消そうともがいた話

CloudformationのTemplateを触っていた際、リソースベースポリシーの変更ミスがきっかけで、誰もアクセスすることのできないs3 bucketを生み出してしまいました。

このリソースを消そうと奮闘する中でAWSのアクセス制御について色々と学ぶことがあったので、ここにまとめようと思います。

# 発生した問題

以下のような形で、s3 bucketとそのbucket policyを他のリソースと共に一つのstackに載せて管理しようとしていました。

``` yaml
  S3Bucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Sub ${AWS::StackName}-bucket
  S3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3Bucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Action: s3:*
            Effect: Deny
            Principal:
              AWS: "*"
            Resource:
              - !Sub arn:aws:s3:::${S3Bucket}
              - !Sub arn:aws:s3:::${S3Bucket}/*
            Condition:
              StringNotLike:
                aws:userId:
                  - !Join
                    - ":"
                    - - !GetAtt BuildServiceRole.RoleId
                      - "*"
```
ここで宣言しているbucket policyでは、ある特定のロールを除いた他の全ての場所からのアクセスを明示的に拒否しています。

本来はcloudformation経由のアクセスを許可するポリシーを他に設定しておくべきでしたが、設定が漏れてしまっていました。

このstackを削除しようとすると、当然問題のs3 bucketとそのbucket policyが削除できずに処理が失敗します。

stackの削除を試みたタイミングで唯一bucketへのアクセス権限を持っていたロールも消されてしまっているため、こうなってしまうとこの問題を通常ユーザの側だけで解決することは困難になります。

# 試したこと

ゾンビ化してしまったバケットを削除しようと色々と試してみたものの、AWSの堅牢なアクセス制御の前にどうすることもできず、結局ルートユーザにお願いするまでの流れは以下の通りでした。

1. `s3api delete-bucket` → AccessDenied
2. `s3api delete-bucket-policy` → AccessDenied
3. templateを書き換えてdeploy 
→ `DELETE_FAILED`状態なのでupdate不可。stack削除試行前に行ったとしてもAccessDenied
4. bucketへのアクセス権限を持っていたロールを再生成
→ 「ホワイトリストに指定している文字列にマッチするロールを新しく作れば良いのでは？」と考えたものの、ロールのIDはリソース名やARNと違ってAWSによって自動生成される識別子のため、ユーザに指定の余地はありませんでした。

``` yaml
        Condition:
          StringNotLike:
            aws:userId:
              - !Join
                - ":"
                - - !GetAtt BuildServiceRole.RoleId
                  - "*"
```
この時点で諦めてルートユーザにお願いして消してもらいました（判断が遅い）

# 学び
普段の業務ではIAMユーザ（グループ, ロール）の側に紐づけるポリシーの方を気にすることが多いですが、今回はリソースに直接紐づけるポリシーの設定が原因となって問題が発生していました。

このように二つの種類のポリシーが互いに補完し合う形でリソースへのアクセスを制御しているイメージを今まで持てていなかったため、これらのポリシーについて参考となるサイトを見ながらそれぞれの働きを整理しました。

## 二種類のポリシー

[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/access_policies_identity-vs-resource.html)では、IAMユーザ（グループ, ロール）側に紐づけるポリシーのことをアイデンティティベースのポリシー、リソースに直接紐づけるポリシーのことをリソースベースのポリシーと呼んでいます。

### アイデンティティベースのポリシー
IAMユーザ（グループ, ロール）側にアタッチされるポリシーで、「このユーザ（グループ, ロール）はどのリソースにアクセスでき、どんなアクションを行えるのか」を指定します。

Statement内ではEffect, Actionの他に、その対象（どのリソースに対して適用するか）を**Resource**で指定することが一般的です。

```yaml
        Statement:
          - Effect: 'Allow'
            Action: 's3:ListBucket'
            Resource: !GetAtt MyS3Bucket.Arn
```

### リソースベースのポリシー
リソースに直接アタッチされるポリシーで、「このリソースにはどのユーザ（グループ, ロール）がアクセスでき、どんなアクションを行えるのか」を指定します。

Statement内ではEffect, Actionの他に、その対象（どのエンティティからの操作に対して適用するか）を**Principal**で指定することが一般的です。

```yaml
        Statement:
          - Effect: 'Allow'
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:role/${MyIAMRole}'
            Action: 's3:GetObject'
            Resource: !Sub 'arn:aws:s3:::${MyS3Bucket}/*'
```

リソースベースのポリシーを適用できるAWSサービスは[こちら](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html)にまとまっています。

アイデンティティベースのポリシーと一緒に使用することで、AWSリソースに対するより柔軟で堅牢なアクセス制御を実現できるのでした。

## リソースベースポリシーの運用で気をつけたいこと
リソースベースのポリシーをアタッチする際に設定を間違えると、今回のように「誰からもアクセスできず、変更・削除もできない」リソースが生まれてしまい、ルートユーザに頼らざるを得なくなる可能性があることには気をつけたい所です。

リソースベースのポリシーでは、そのリソースに紐づくポリシーの変更に関する権限(例: `s3:PutBucketPolicy`, `sns:SetTopicAttributes`など)も設定できることが一般的に思われます。

その場合は今回のようにポリシーで全てのアクセスをDenyしてしまうと、「リソースもそのポリシーも変更・削除ができない」というどうしようもない状態に陥ってしまいます。

Denyを含むリソースベースのポリシーを作る際は、緊急用に信頼できる場所からのアクセス経路を設定しておくなど、慎重に構築して行くことを心がけようと思いました。

# まとめ

ゾンビ化してしまったバケットを消そうともがいた話に始まり、AWSリソースのアクセス制御の方式や、リソースベースのポリシーを運用する際の注意点についてお話しして来ました。理解の一助となれば幸いです。ここまで読んでいただきありがとうございました。

# 参考サイト
https://dev.classmethod.jp/articles/delete-access-denied-bucket-policy/

https://qiita.com/Shoma0210/items/17193f254180396fb8e1