Amazon CloudFront の署名付きURL(既定ポリシー)を使って、安全にコンテンツを配信してみた

# Note
カスタムポリシーを利用した記事もあります。
https://qiita.com/sugimount-a/items/53febd43e4cad7ffdb54


# はじめに

Web アプリケーションやモバイルアプリなどを提供する際に、画像などの素材を Amazon Simple Storage Service (Amazon S3) に格納したものを配信できます。セキュリティやパフォーマンスを考慮して、以下の 2 点を実現していきたいユースケースがあります。

- S3 に格納したファイルは、一部のユーザーに限定して配信したい
- たくさんのユーザーがいるため、CloudFront を使った配信をしたい



上記を満たすために、CloudFront の署名付き URL や 署名付き Cookie が活用できます。S3 を Private Bucket にしたうえで、CloudFront の署名付き URL や 署名付き Cookie を生成することで、CloudFront のスケーラビリティを活かしながら、一部のユーザーに限定したアクセスが提供できます。Web アプリケーション側で、何らかの認証機能を利用しながら、ログイン済みユーザーに限定して署名付き URL ・署名付き Cookie を発行する仕組みを作り上げることで、実現頂けます。

次の構成図に、概要図を載せています。

1. ユーザーは、Web アプリケーションに備わっている認証機能でログインを行う
2. Web アプリケーションはリクエストに応じて、画像データなどに紐づく CloudFront 署名付き URL を生成
3. Web アプリケーションは、ユーザー側に署名付き URL をレスポンス
4. ユーザは、署名付き URL にアクセスすることで、Private な S3 Bucket に格納されている画像データなどを取得

![image-20220225194535428.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3e2fe9a3-456a-10ea-600d-a59a5b12b175.png)



概要図では、Web アプリケーション側に秘密鍵を持たせて署名付き URL を生成していますが、API Gateway + Lambda を活用したサーバーレス構成の選択肢が有ります。サーバーレス構成は運用負担の軽減といったメリットがあります。



# 2種類の署名付き URL

署名付き URL は、セキュリティに関するポリシーの違いによって、「規定ポリシー」と「カスタムポリシー」の 2 種類あります。2 つのポリシーの違いを AWS Document に書かれている表から抜粋します。規定ポリシーは、デフォルトの設定になっており、手を加えない分比較的楽に生成できます。一方、カスタムポリシーは自分たちのワークロードに合わせたカスタマイズが可能で、柔軟にコントロールができます。カスタムポリシーの方は、複数のオブジェクトのために再利用出来たり、アクセス元の IP アドレスの制限が出来るため、便利に利用できそうですね。

![image-20220225195842898.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/6d3f6f4b-46fe-ec29-acb8-af37909318ee.png)

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html#private-content-choosing-canned-custom-policy



今回の記事では、規定ポリシーを使った署名付き URL を生成する方法を確認していきます。



# 秘密鍵・公開鍵の作成

署名付き URL を生成するために、秘密鍵・公開鍵の生成が必要です。手元の Linux マシンなどで作成していきましょう。

```
mkdir ~/temp/cloudfront-presign/
cd ~/temp/cloudfront-presign/
```



秘密鍵を生成します

```
openssl genrsa -out private_key.pem 2048
```



秘密鍵から、CloudFront に登録するための公開鍵を生成します

```
openssl rsa -pubout -in private_key.pem -out public_key.pem
```



こんな感じに生成できました

```
> ls -lha
total 12K
drwxr-xr-x  2 ec2-user docker   51 Feb 25 20:18 .
drwxrwxr-x 36 ec2-user docker 4.0K Feb 25 19:12 ..
-rw-r--r--  1 ec2-user docker 1.7K Feb 25 20:18 private_key.pem
-rw-r--r--  1 ec2-user docker  451 Feb 25 20:18 public_key.pem
```



# CloudFront に公開鍵をアップロード

CloudFront のページに移動して、Create public key を押します

![image-20220225202106137.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4c578294-dc06-69af-2262-95ea4110d6b2.png)



公開鍵の中身を確認して、登録をしていきます

```
cat public_key.pem
```

![image-20220225202319453.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/3d0de167-f804-e68a-0513-317e3a8fb2b3.png)



登録されました。

![image-20220225202407849.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/dc7b57e8-c9c2-c2c0-e4f3-c41bc7217f81.png)



# CloudFront に Key Group を作成

Key Group を作成して、アップロードした公開鍵を登録します

![image-20220225202500082.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/5efc465d-b602-7cd5-2a97-08ded5ccf8cc.png)



名前や公開鍵の名前を選択します

![image-20220225202545446.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/af4fbfb8-df01-fd2e-34d1-ccb64522e4f0.png)



作成されました

![image-20220225202614643.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/d671bee9-1e2b-3fca-bd39-a547c40a2686.png)



# S3 Bucket の作成

適当な名前で Private な S3 Bucket を作成して、うちの猫の写真をアップロードします。この写真を、署名付き URL のアクセス確認に使っていきます。

![image-20220225203426231.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b6596ca4-3ca4-c0e1-c128-c124e84f7e3a.png)



# CloudFront の Distribution の作成

署名付き URL を利用するために、Create distributon を押します。

![image-20220225203558305.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/1ddaf6d9-df9c-3c09-bdc0-2765ba9e60f1.png)



- Distribution の Origin として、作成した S3 Bucket を指定
- OAI を利用して、S3 Bucket のアクセスを CloudFront のみに制限

![image-20220225203852078.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/6951b0d8-326d-e527-d10d-595b8401d227.png)



Restrict viewer access を有効にして、署名付き URL が無いとアクセスできないように設定します

![image-20220225204031423.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/80768683-2f45-da41-aa9b-cc03f0243fe8.png)



Create Distribution を押します

![image-20220225204128034.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/660cd827-0a67-98c7-45b5-c17c205dd0cf.png)



Deploy プロセスが走りました

![image-20220225204209601.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0f8cc7e1-f8ff-b11f-d8af-d3b48c993210.png)



# 署名付きURLの生成

今回は、AWS CLI を使って CloudFront の署名付き URL を生成していきましょう。実際の環境では、SDK を使ったプログラムから署名付き URL を生成すると思います。次の Document にサンプルコードがあるので参考にできます。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/PrivateCFSignatureCodeAndExamples.html

```
aws cloudfront sign \
--url https://d31z1elfcc2o1s.cloudfront.net/mycat.jpg \
--key-pair-id K1DYHGI8XWLXZL \
--private-key file:///home/ec2-user/temp/cloudfront-presign/private_key.pem \
--date-less-than 2022-02-25T20:50:00+09:00
```



実行例

```
> aws cloudfront sign \
      --url https://d31z1elfcc2o1s.cloudfront.net/mycat.jpg \
      --key-pair-id K1DYHGI8XWLXZL \
      --private-key file:///home/ec2-user/temp/cloudfront-presign/private_key.pem \
      --date-less-than 2022-02-25T20:50:00+09:00
https://d31z1elfcc2o1s.cloudfront.net/mycat.jpg?Expires=1645789800&Signature=lteo65gt~7K2UFPyG9KEOhIEkTYid2Zn9IKG6Ync605wwnygvo~dUWjwX~N~8BmGiAPnMavtADL-mf-24aGRQa~xRSgLm6tPvoyqeX6rSptXvkrPMqtQ9HOiaHPMY8PuzMkj3CQLb-0O53CwD5HJNUeQbAers6IFvxeJoEgtWTn1YE8J1umezTxHlsHuRl7Eq0soT8iktFXaRKE0AQFjYvT5UvmkgShA2j7eqtcIW~mmKHT2LQ5XSSxztiZj5OWzFXLyET9va0Amj2t7~EOxA21n~7AWamrmU9xNCKO5bnuz31dV4L5fKSVQdxl5jFddXk6~qyxDP2r26JgXtYFMJA__&Key-Pair-Id=K1DYHGI8XWLXZL⏎
```



# アクセス確認

## 通常のアクセス

まず、通常通り、署名付き URL なしで CloudFront にアクセスしてみます。想定通り、エラーになりました。

![image-20220225204956139.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/557b600b-19e9-6778-757a-f3c5981791ad.png)



## 署名付き URL でアクセス

AWS CLI で生成した URL にアクセスしてみます。S3 Bucket にアップロードした、家猫の写真が見えました。かわいい。

![image-20220225205102181.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0404bbbc-52dc-2829-aaec-62cc1a0f4f00.png)



## 有効期限切れ

署名付き URL の有効期限が切れた後にアクセスしてみた結果です。これも想定通りエラーになりました。

![image-20220225205130622.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/0413010d-83e2-35d0-c016-1a9aa488134a.png)



# 検証を通じてわかったこと

- 秘密鍵・公開鍵を生成して、CloudFront に公開鍵をアップロードする必要がある
- 署名付き URL を生成するために、秘密鍵を持っているコンピュートリソース (EC2, Lambda, ECS, App Runner など) を用意する方式が考えられる



# 参考URL

AWS Document : 署名付き URL の使用
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-signed-urls.html

Python のサンプルコード
https://github.com/boto/boto3/blob/develop/boto3/examples/cloudfront.rst

【AWS】CloudFrontで署名付きURLの設定方法（プライベートコンテンツの配信）
https://zenn.dev/may_solty/articles/807dbad3a30de8