S3 MFA Deleteの設定で詰まったところ

# MFA Deleteとは
MFA Deleteは、S3バケット内のオブジェクトを誤って削除しないようにするための仕組みです。
厳密に言えば、MFA Deleteを有効にしてもオブジェクト自体の削除はできてしまうのですが、**オブジェクトのバージョンを削除しようとしたときに、MFAコードの入力を求められるようになります**。

オブジェクトが消えても、バージョンさえ残っていれば、そのオブジェクトを復元することが可能です。
>バージョニング対応のバケットからオブジェクトを削除すると、Amazon S3 はそのオブジェクトの削除マーカーを作成します。削除マーカーがオブジェクトの最新バージョンになり、実際のオブジェクトが以前のバージョンになります。削除マーカーを使用すると、Amazon S3 はオブジェクトが削除されたかのようにオブジェクトのリクエストに応答します。例えば、オブジェクトの GET リクエストを送信すると、Amazon S3 はエラーを返します。
>
>バージョニング対応のバケットから削除されたオブジェクトは、次のいずれかの方法で取得できます。
>- 以前のバージョンのオブジェクトをダウンロードする
>- 削除マーカーを削除する: 削除マーカーを削除すると、実際のオブジェクトがオブジェクトの最新バージョンになります。

（出典）[バージョニングが有効なバケットで削除された Amazon S3 オブジェクトを取得するにはどうすればよいですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-undelete-configuration/)

MFA DeleteはS3のバージョニング機能のオプションの位置づけです。
MFA Deleteを有効化するためには、前提としてS3バケットのバージョニングを有効にしておく必要があります。


# この記事について
この度、下記の記事を参考に、S3バケットのMFA Delete有効化を行い、そのバケットを削除するところまでやってみました。

https://qiita.com/miyuki_samitani/items/626020813350b2ccbfa3

いくつか躓いたところがあるので、備忘を兼ねて記録しておきます。
※躓いたポイントは私の作業環境に起因するものです。
　記事に記載されていることに誤りなどはないことを強調しておきたいと思います。
　良記事でした。ありがとうございました。


# ルートアカウントのアクセスキー発行が必要
まず、MFA Deleteの有効化・無効化は、マネジメントコンソールの画面から行うことができません（2022年7月時点）。
設定を確認することはできるが、設定変更はできない。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/731275/47ed1b06-a65a-ffb1-f0b5-a8256efd7019.png)
ではどうやって設定するかといえば、「AWS CLI、AWS SDK、または Amazon S3 REST API」を使用します。

今回私は、記事にもある通りAWS CLIを利用する方式で設定を行いました。
設定にあたっては、
- ルートアカウントでアクセスキーを発行
- アクセスキーIDとシークレットアクセスキーを環境変数に登録
- `aws s3api`コマンドの実行

という手順を踏む必要があります。
ルートアカウントのアクセスキーという最強アイテムを作成することに拒否反応を起こす方も多いと思います。
どうしようもないので渋々作るんですが、怖くて夜も眠れなくなりそうなので、MFA Delete設定後、すぐに消します。

ちなみに私の環境ではルートアカウントのアクセスキーを作ったらアラームを上げる仕組みを作っていたので、当たり前ですがアラームが上がりました。


# aws s3apiコマンドの通信先
MFA Deleteの設定にあたっては、よく見る`aws s3`コマンドではなく、`aws s3api`コマンドを利用します。
`aws s3`コマンドより高度で、細かい設定ができるというイメージでしょうか。

>API レベルのコマンド (s3api コマンドセットに含まれる) は、Amazon Simple Storage Service (Amazon S3) API への直接アクセスを提供し、高レベルの s3 コマンドでは公開されていない一部の操作を有効にします。これらのコマンドは、サービスの機能性への API レベルのアクセスを提供する他の AWS サービスに相当します。

（出典）[AWS CLI での API レベル (s3api) コマンドの使用](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-services-s3-apicommands.html)

この`aws s3api`コマンドを打ったところ、プロキシで弾かれました。
Squidのアクセスログを確認したところ、ばっちりTCP_DENIEDされており、やはりホワイトリストが悪さしてました。

東京リージョンの場合、`aws s3`コマンドを実行すると「s3.ap-northeast-1.amazonaws.com」にリクエストを投げます。
普段は`aws s3`コマンドしか利用しないので、プロキシのホワイトリストでは「s3.ap-northeast-1.amazonaws.com」のみ許可していました。
**一方`aws s3api`コマンドは、「(バケット名).s3.ap-northeast-1.amazonaws.com」にリクエストを投げているようです。**
例えば下記コマンドであれば、「yuki-ink-bucket.s3.ap-northeast-1.amazonaws.com」にリクエストを投げます。
```
aws s3api get-bucket-versioning --bucket yuki-ink-bucket --region ap-northeast-1
```
プロキシのホワイトリストで「s3.ap-northeast-1.amazonaws.com」のサブドメインを許可していなかったため、通信が弾かれたというオチでした。
EC2からインターネットへの通信でプロキシを挟んでいる場合、注意が必要です。

また、コマンドを実行するEC2のリージョンと対象のS3バケットのリージョンが異なる場合、`--region`オプションでリージョンを明示するか、環境変数「AWS_DEFAULT_REGION」を設定するか、`aws configure`コマンドでデフォルトリージョンを変えるかしておきましょう。
リージョンの指定をミスると、「お前が指定したバケット、このリージョンにないんやが？」となり、しばらくプロンプトが返ってきません。。


# ルート権限でも、バケットポリシーでDenyしていれば拒否される
今回の対象バケットは、特定のIAMロール以外からのアクセスをDenyするバケットポリシーを設定していました。
コマンドを実行するEC2にアタッチされているIAMロールからのアクセスは許可していたので、普段実行する`aws s3 cp`コマンドなんかは問題なく実行できていました。

ただ、今回はルートアカウントのアクセスキーIDとシークレットアクセスキーを環境変数に登録している状況のため、ルートアカウントとしてコマンドが実行されます。
結果、バケットポリシーによりDenyされました。
※AWS CLIの認証情報の優先順位は「[構成設定と優先順位](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-precedence)」を参照してください。

話は変わりますが、以前、VPCエンドポイント以外からのアクセスをDenyするS3バケットにCloudFormationのテンプレートを配置し、ルートアカウントでマネジメントコンソールにログインし、CloudFormation画面からそのテンプレートを実行しようとしたことがあります。
その時も権限不足でエラーが出ました。
バケットポリシーはルート権限より強し。。
※まあルートアカウントなので、そのバケットポリシー書き換えられちゃうんですけど。


# バージョンを消すのが面倒くさすぎる
[記事](https://qiita.com/miyuki_samitani/items/626020813350b2ccbfa3)を参照しつつ、上記の問題をクリアし、無事MFA Deleteの有効化に成功。
テスト用のオブジェクトを作成し、そのオブジェクトのバージョンを削除できないことを確認しました。
やりたいこと終わったしバケット削除するかと思ったのですが、これが大変でした。

**S3バケットそのものを削除する際、その前提として、バケット内のオブジェクトとバージョンを全て削除しておく必要があります。**
オブジェクトの削除はまあ良いとして、バージョンの削除は1つ1つバージョンIDを指定して削除する必要があるようで、やってられるかと。

まずMFA Deleteを無効化し、下記の記事を参考に全てのバージョンを一括削除しました。

https://qiita.com/charon/items/d1dafba531714f6fddc4

こちらのスクリプトは最強でした。ありがとうございました。
```
#!/bin/bash

for BUCKET_NAME in "$@"
do
    aws s3api list-object-versions --bucket ${BUCKET_NAME} |   grep -E '"(Key|VersionId)"' |   perl -wp -e 's!.+("Key".+)\r?\n!$1!' |   perl -wp -e 's!"Key": "(.+)", + "VersionId": "(.+)",!aws s3api delete-object --bucket '${BUCKET_NAME}' --key $1 --version-id $2!' |   perl -wp -e 'system($_)'
done
```

# 最後に
ルートアカウントのアクセスキーと環境変数の削除は忘れずに！！
```
export -n AWS_ACCESS_KEY_ID
export -n AWS_SECRET_ACCESS_KEY
```


# 参考
・[バージョニングが有効なバケットで削除された Amazon S3 オブジェクトを取得するにはどうすればよいですか?](https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-undelete-configuration/)
・[MFA 削除の設定](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/MultiFactorAuthenticationDelete.html)
・[S3 MFA delete とは](https://qiita.com/miyuki_samitani/items/626020813350b2ccbfa3)
・[AWS Command Line Interface - 設定の基本](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html#cli-configure-quickstart-precedence)
・[バージョニングを有効にしたS3で、オブジェクトを一気に削除したい](https://qiita.com/charon/items/d1dafba531714f6fddc4)