Privatelink for S3でクロスアカウントアクセスを試してみた


今年の2月にGAとなったS3用のPrivatelinkを使用してクロスアカウントでのアクセスを試してみました。

<a href="https://aws.amazon.com/jp/blogs/news/aws-privatelink-for-amazon-s3-now-available/">AWS PrivateLink for Amazon S3 の一般提供を開始</a>

#概要図
書くまでもないといえば無いですが、このようなイメージです。
シンプルな構成で検証してます。
![プレゼンテーション1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/972546/c3a27a15-dd98-4ec4-53d3-288030bcec62.jpeg)

#気になっていたこと
検証前の段階では、以下の点を気にしていました。

+ Privatelinkはどちらのアカウントで作成必要か？
+ S3のバケットポリシーへの許可の書き方は通常通りでいいのか？
+ サーバーサイド暗号化されている場合、明示的な許可は必要か？
+ IAMへの許可は通常通り必要か？

他のPrivatelinkは構築したことがあったので大丈夫だろうとは思いつつも、S3用のPrivatelinkは他と少し使用が違うので検証はしておこうと思いやってみました。

参考：
<a href="https://blog.serverworks.co.jp/only-thing-that-makes-Amazon-S3-PrivateLink-different-from-other-services">Amazon S3 の PrivateLink が他のサービスと異なるたった1つの点</a>

なお、当記事ではPrivatelink、EC2、S3といったサービス自体の説明は割愛します。

#検証
では、検証した内容です。

##1.アカウントB側のS3バケット作成
アカウントB側で適当な名前でS3バケットを作成します。
まずは東京リージョン（ap-northeast-1）で作成。
設定は基本デフォルトですが、__「サーバーサイドの暗号化（SSE-S3）」__は有効にしました。

また、バケットポリシーは以下の通り設定し、クロスアカウントするロールからのアクセスを許可します。

```ruby:バケットポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "<アカウントAのRole-ARN>"
            },
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:PutObjectAcl",
                "s3:DeleteObject",
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::<アカウントBのdest-bucket-name>",
                "arn:aws:s3:::<アカウントBのdest-bucket-name>/*"
            ]
        }
    ]
}
```

##2.アカウントA側でEC2インスタンス作成
Administratorアクセスを付与したIAMロールを先に作成してありましたので、そちらを付与しました。

##3.アカウントA側でS3用のPrivatelinkを作成
本命の手順です。
通常のPrivatelink同様、アカウントA側で作成してみます。

サービスではInterface型を選択。
![スクリーンショット 2021-06-07 10.11.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/972546/aa14d2f0-1eda-7c2a-9b1f-c11066966d76.png)

他のサービスだとプライベートDNSの有効/無効を設定することが出来ますが、S3用のPrivatelinkは設定できません。
![スクリーンショット 2021-06-07 10.11.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/972546/cc05fcb4-0ec2-583e-e1dd-c477e3a5a041.png)

詳しくは以下。めっちゃ参考にさせていただきました。
<a href="https://dev.classmethod.jp/articles/s3-privatelink-diagram/">「Amazon S3 インターフェースエンドポイント（PrivateLink）ではプライベート DNS をサポートしていません」 の意味を絵をかいて腹落ちさせてみた</a>

あと、セキュリティーグループの設定がありますが今回は

+ インバウンドはEC2にアタッチしたセキュリティーグループのIDからのHTTPS通信を許可
+ アウトバウンドは全許可

で検証しています。
実運用の際はセキュリティーポリシーに則って設計してください。

上記で無事に作成できました。
![スクリーンショット 2021-06-07 10.13.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/972546/3c50ea99-6df2-695d-a796-e722a9e15b9f.png)

##4.動作確認（Privatelink）
一旦Privatelinkの動作確認をします。
EC2から作成時に払い出されたDNS名宛に名前解決ができるかを確認。

```ruby:PrivatelinkDNS名確認
[ec2-user@test-instance ~]$ dig *.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com +short
172.31.32.xx
172.31.1.xxx
172.31.22.xxx
```

IPアドレスが返却されればOK。
そうでない場合は、セキュリティーグループの設定等見直しましょう。

Privatelinkを経由してS3を参照する際は_--endpointurl_オプションで指定します。
_https://bucket_〜のリンクを付与してから始まり、以下はDNS名を参照させる点が注意。

```ruby:アカウントAバケット参照
[ec2-user@test-instance ~]$ aws s3 ls --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
2021-06-07 00:51:48 test-bucket
2021-03-25 08:56:17 hogehoge
2021-06-07 01:00:57 fugafuga
```

##5.動作確認（クロスアカウント）
では、クロスアカウントでの動作を確認します。

```ruby:アカウントBバケット参照
[ec2-user@test-instance ~]$ aws s3 ls s3://dest-bucket --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
[ec2-user@test-instance ~]$
```

まだファイルは何も置いていないので、エラーが出なければ成功です！
バケットポリシー、IAMなどが許可できていないと以下のようなエラーが発生します。

```ruby:
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
```

続いて、書き込み等も続けて試験。

```ruby:
[ec2-user@test-instance tmp]$ echo This is test. > test.txt
[ec2-user@test-instance tmp]$ ll
合計 4
drwx------ 3 root     root     17  6月  7 01:03 systemd-private-1f89a79cd0fa4491b31ca1d48e75b2d9-chronyd.service-gTobSU
-rw-rw-r-- 1 ec2-user ec2-user 14  6月  7 01:28 test.txt
[ec2-user@test-instance tmp]$ aws s3 cp ./test.txt s3://dest-bucket --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
upload: ./test.txt to s3://dest-bucket/test.txt
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$ aws s3 ls s3://dest-bucket --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
2021-06-07 01:29:47         14 test.txt
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$ aws s3 rm s3://dest-bucket/test.txt --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
delete: s3://dest-bucket/test.txt
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$ aws s3 ls s3://dest-bucket --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
[ec2-user@test-instance tmp]$
```

すべての動作が問題なく行えました！

#結論…？

+ Privatelinkはどちらのアカウントで作成必要か？
→アカウントA（参照元）側で作成必要でした。

+ S3のバケットポリシーへの許可の書き方は通常通りでいいのか？
→通常通り、アカウントA側EC2にアタッチしたロールへのアクセス許可でOKでした。

+ サーバーサイド暗号化されている場合、明示的な許可は必要か？
→明示的な許可は不要でした。


+ IAMへの許可は通常通り必要か？
→…あれ？

「めっちゃ早く検証終わったじゃん、ラッキー」
なんて考えていたのですが、そういえばIAMへの許可出来て無くないか？

##内容整理

再度確認しましたが、アカウントAのEC2にはAWSマネージドのAdministrator Accessを許可したポリシーしかありませんでした。

アカウント跨ぎの場合は許可必須かと思っていたのですが…
参考：
<a href="https://dev.classmethod.jp/articles/summarize-principal-settings-in-s3-bucket-policy/">S3のバケットポリシーでハマったので、S3へのアクセスを許可するPrincipalの設定を整理する</a>

挙動的には問題なく見える。
ということは、バケットポリシーさえ設定できていれば、Privatelink経由ならアカウント跨ぎでもIAMでの明示的な許可は不要…？？？

バケットポリシーを外したところエラーが出たので、ここで許可を与えていることは間違いなさそうです。

散々考えてますが今の所はっきりとこれだから出来たんだ！という答えは見つかっておらず、、
もしどなたか詳しい方がいらっしゃいましたら、教えて下さい。

#結論＋補足

+ Privatelinkはどちらのアカウントで作成必要か？
→アカウントA（参照元）側で作成必要でした。

+ S3のバケットポリシーへの許可の書き方は通常通りでいいのか？
→通常通り、アカウントA側EC2にアタッチしたロールへのアクセス許可でOKでした。

+ サーバーサイド暗号化されている場合、明示的な許可は必要か？
→明示的な許可は不要でした。

+ IAMへの許可は通常通り必要か？
→__無くても動作はしましたが、実運用の際は事前に検証することをおすすめします。__

#おまけ（別リージョンへのアクセス）
バケットを別リージョンに作成し直してアクセスしてみました。

元バケットを消してすぐ同じ名前で作成し直そうとしたところ、怒られました、、

```ruby:
A conflicting conditional operation is currently in progress against this resource. Please try again.
```

気を取り直して、2をつけてオレゴン（us-west-2）で再作成します。
条件は同様です。

同じPrivatelinkから書き込み等試しまして、問題なく動作しました。

```ruby:クロスアカウント・クロスリージョンアクセス
[ec2-user@test-instance tmp]$ aws s3 ls s3://dest-bucket2 --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$ aws s3 cp ./test.txt s3://dest-bucket2 --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
upload: ./test.txt to s3://dest-bucket2/test.txt
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$ aws s3 ls s3://dest-bucket2 --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
2021-06-07 01:36:50         14 test.txt
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$ aws s3 rm s3://dest-bucket2/test.txt --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
delete: s3://dest-bucket2/test.txt
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$ aws s3 ls s3://dest-bucket2 --endpoint-url https://bucket.vpce-xxxxxxxxxxxxxxx-xxxxxxx.s3.ap-northeast-1.vpce.amazonaws.com
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$
[ec2-user@test-instance tmp]$ aws s3 ls s3://dest-bucket2
[ec2-user@test-instance tmp]$
```

S3用のPrivatelink、IAMの件以外は普通に使えそうで良かったです。
余裕があれば後日再検証して追加記事投稿するかもしれません。