AWS CloudFrontとS3を使ってパスワード付きで動画を配信する(2024年10月時点)

# 1. この記事の内容を２行で
* 社内LTの動画を社内に向けて配信したくなったので、AWSのS3とCloudFrontを使って配信を行った
* 配信の際、単純な公開だと社員以外の人にも見えてしまうため、簡単な基本認証をかけた

本記事と似たようなことをしている既存の記事はあるのですが、すでに情報が古いものがあったため、本記事であらためて知見を整理しました。

【処理概要図】
```mermaid
graph LR
  subgraph "インターネット"
    user["リクエスト"]
  end
  user <--> CloudFront(1.ユーザー)
  subgraph AWS
    subgraph "CloudFront エッジロケーション"
      cloudfrontEdge["CloudFront<br> (エッジロケーション)"]
      cloudfrontEdge -- 2.キャッシュにヒット <--> user
      cloudfrontEdge -- 3.キャッシュにない場合 --> CloudFrontOrigin
    end
    subgraph "CloudFront オリジン"
      CloudFrontOrigin["CloudFront<br> (オリジン)"]
      CloudFrontOrigin -- 4.署名付きURLへリクエスト --> S3Bucket
    end
    S3Bucket["S3 バケット<br>(動画ファイル)"]
    S3Bucket -- 5.動画データを返す --> CloudFrontOrigin
    CloudFrontOrigin -- 6.データをキャッシュして返す --> cloudfrontEdge
  end
```

# 2.前提知識
　本記事を理解するための背景知識となる基本的な用語を簡単に説明します。すでにご存知の方は、本項目はスキップしてください。

 ## 2-1. CloudFront
　AWSが提供するCDN(contents delivery network:コンテンツ配信ネットワーク) サービスです。ウェブサイトや、動画ストリーミングなどのコンテンツを世界中のユーザーに高速かつ安全に配信することができます。普通のWebサイトによる配信と違う点はエッジロケーションと呼ばれる分散されたデータセンターを経由する配信機能によって高速で安価な配信ができ、トラフィック負荷にも柔軟な対応ができることです。またDDoS攻撃への対応も自動的に行われるためセキュリティ面でのアドバンテージもあります。

 ## 2-2. ディストリビューション
 　ここでいうディストリビューションとは、AWS CloudFront で設定するコンテンツを配信するための取扱い単位です。 具体的には、以下の要素を定義します。
* __配信するコンテンツの場所__: S3 バケット、EC2 インスタンスなど。今回はS3を設定
* __コンテンツのキャッシュ方法__: TTL (有効期限) やキャッシュ有無の設定。今回は特別な設定なし
* __アクセス制御__: 地理的な制限や署名付き URL など。今回はS3への署名付きURLによるアクセスを行う
* __HTTPS 設定__: HTTP/HTTPSの使用。今回はHTTPS

等々…今回は単純に動画配信できればいいので細かい設定はいじりません。

 ## 2-3. OAC(Origin Access Control)
　OACとはCloudFront がオリジンサーバー(今回はS3バケット)からコンテンツを取得する際に、CloudFront 以外からのアクセスを制限するためのセキュリティ機能です。OACは、オリジンサーバーへのアクセスを制御するために __署名付きURL__ を使用します。CloudFront がオリジンサーバーにリクエストを送信する際、署名付きリクエストを生成します。 この署名には CloudFront ディストリビューションの情報や有効期限などが含まれています。オリジンサーバーは受信したリクエストに含まれる署名を検証し、署名が有効であればリクエストを許可しコンテンツを返します。

　署名付きURLはアクセスを許可したいプライベートなリソース（今回例ではS3上のファイル）に安全確実にアクセスできるようにするためのURLで、通常のURLにデジタル署名が追加されたものです。署名は秘密鍵を使って生成されます。この署名によってアクセス元（今回はCloudFront）が想定された正しいものであることを証明し、同時に通信内容に改竄がないことを証明します。

 ## 2-4. OAI(Origin Access Identity)
　OAIは、OACが採用される前にAWS CloudFront がオリジン (S3 バケットなど) にアクセスする際使用されていた特別な CloudFront ユーザーです。今回の調査をした際 Web上の多くの情報源が OACではなくOAIを使って署名付きURLを構成していましたが現在のCloudFront ディストリビューションのオリジン設定では、OAIはレガシーとなっており、よりセキュリティ機能の強化されたOACを使うことが推奨されています。

 以上で、前提知識の説明は終了です。ここからは、設定の手順を説明します。

 # 3. おおまかな設定手順
 設定の手順は以下のようになります。
 
1. S3 に動画ファイルをアップロード
2. CloudFront に S3 の動画ファイルを配信するためのOACを作成
3. CloudFront に Distribution を作成
4. 署名付きURLを生成して、動画ファイルにアクセス可能な状態にする
5. CloudFront Functionsを使って簡単な基本認証ロジックを組み込む

以下の項目でこれらの各手順を細かく説明します。

# 4. S3に配信したい動画ファイルをアップロード
特に難しい所はないです。S3バケットはプライベートなものをひとまずデフォルトの設定で作り、動画ファイルをアップロードします。今回は lt_haishin という名前で作成し、テスト用の動画を1本アップロードしました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/aa96f0eb-20b7-992a-2036-de4f10107fe7.png)

# 5. CloudFlont にOACを作成する
　CloudFront で S3 の動画ファイルを配信するためにOACを作成します。従来のこの種の設定ではここでOAIを設定すべしとなっている技術記事が多いようです。ところが実際にAWSコンソールでCloudFrontの画面を見ると、OAIの設定は「(レガシー)」となっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/2543eda5-5291-3188-03e4-ccfa2677e218.png)
オリジンアクセス設定の仕様が新しくなっており、OAIの仕様は古いものなので使わないことが推奨されています。下記の記事によると2022年8月頃にすでにそうなっているようです。

https://dev.classmethod.jp/articles/amazon-cloudfront-origin-access-control/

>OACの発表とあわせて、CloudFront Developer Guide上ではこれまでのOAIによるアクセス制限方法は「legacy, not recommended」と記載されるようになりました。OAI自体がすぐに利用できなくなるということではありませんが、これからS3へのアクセス制限を実施する場合には新しいアクセス制限方法であるOACを使うのがよいでしょう。

下記がこの件についての公式ドキュメントです。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html

以下、具体的な設定を行います。

## 5-1. OAC設定
OAC作成をする設定ダイアログです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/35fba088-4ffd-746d-adb5-eabbe1b58750.png)
名前と説明は適当に入力します。署名動作は「署名リクエスト（推奨）」とします。この署名リクエストとは先に説明したとおり、CloudFront からそのオリジンであるS3パケットへのリクエストに署名を付与するという意味です。つまり署名付きURLを使ってオリジンにアクセスします。「認証ヘッダーを上書きしない」のチェックはオフのままとします。オリジンタイプは今回はS3とします。

無事作成できれば以下のような画面となります。（各IDは伏せました。ご了承下さい）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/6fc77dd5-5f3a-4fa6-3bdc-5c2c56fd6679.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/3e4ffa83-d42b-54e9-bb2b-7746f95a2081.png)

OAIを使う場合はここでキーペアを作成してOAIに設定する手順がありましたが、OACを使う場合は不要です（裏でやってくれるらしい）。

# 6: CloudFront にディストリビューションを作成
　ディストリビューション作成時のコンソール画面は下図のようになっています。Origin domain として、上で作成したS3のドメイン名を指定します。Origin Pathは指定しなければS3のフォルダそのままとなるので入力不要です。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/b253e848-d6d0-91f1-c8cb-6a78da652d75.png)

オリジンアクセスの設定では、さきほど作成したOACを指定します(Origin access control settingsのラジオボタンを選択)。 ここで「S3パケットポリシーを更新する必要があります」というメッセージが表示されます。S3バケット側にCloudFrontからのアクセスを許可するためのポリシーを更新しないといけないようです。このことは覚えておいて、先に進みます。

【ディストリビューション作成後の表示】
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/43cbc1c6-19de-9a76-1e92-3d0ad1dcb3a3.png)

ディストリビューション作成後、S3バケットポリシーを更新するためのコピー用のボタン（画像右上の「ポリシーをコピー」）が表示されますので、それをクリックすると、カスタムポリシーを表すJSON情報がクリップボードへコピーされます。

## 6-1. S3バケットのポリシー設定
コピーされたJSONはこんな感じです。(※具体的なIDは伏字にしています)
``` JSON:コピーされた内容
{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "AllowCloudFrontServicePrincipal",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::lt-haishin/*",
            "Condition": {
                "StringEquals": {
                    "AWS:SourceArn": "arn:aws:cloudfront::999999999999:distribution/ZZZZZZZZZZZZZZ" 
                }
            }
        }
    ]
}
```

このポリシーをS3バケットに適用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/517b1f99-421a-7ede-45ea-888148432be6.png)

バケットポリシーの「編集」ボタンをクリックし、先ほどコピーしたJSONを貼付けて「保存」ボタンをクリックします。

# 7.実際のURLから動画ファイルにアクセスする

実際に下記のようなURL（例）にアクセスしてみます。ここまでがうまくできていれば、アクセスできるはず。

```
https://<生成された文字列>.cloudfront.net/lt_prelude.mp4
```

このURLのサーバー部分はディストリビューション詳細画面の「ディストリビューションドメイン名」に表示されている文字列です。lt_prelude.mp4 は今回のテスト用動画のファイル名です。S3側でフォルダを切った場合はそのフォルダ名も必要です。

URLを打ち間違うとブラウザ画面は下記のような Access Denied XMLタグが表示された画面となります。またOACの設定で署名動作を「リクエストに署名しない」としている場合も同様に Access Denied となりました。署名動作は「署名リクエスト（推奨）」に合わせておく必要があります。

```
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>22TH..(中略)..1FM</RequestId>
<HostId>QXcarInlg...(中略)...MemazIk=</HostId>
</Error>
```

無事にアクセスできるとブラウザ画面に動画が表示されます。

# 8.CloudFront Functions を使って基本認証ロジックを組み込む
　ここまででCloudFrontとS3を使った動画配信はできたのですが、もう一つやることがあります。今回は社内向けの配信なので、社員だけが動画を閲覧できるようにしたいわけです。そのために最低限の仕組みとして、CloudFront Functionsを使ってhttpの基本認証（Basic認証）を組み込み、パスワードを（連絡され）知っている社員だけが動画を閲覧できるようにします。

 ## 8-1. CloudFront Functions とは？
　CloudFront Functions は、Amazon CloudFront のエッジロケーションで実行できる軽量な JavaScript 関数を実行できる仕組みです。これにより、オリジンサーバーに負荷をかけることなく、コンテンツ配信を動的に制御・カスタマイズできます。いわゆるエッジコンピューティングを実現する仕組みです。

## 8-2. CloudFront Functionsでできること
__HTTP リクエストとレスポンスのヘッダーの変更__:
* Cookie、キャッシュ制御ヘッダー、CORS ヘッダーなどを操作できます。これにより、キャッシュの動作を制御したり、セキュリティを強化したりできます。

__URL の書き換え (リダイレクト、リライト)__:
* 特定の URL へのアクセスを別の URL にリダイレクトしたり、URL の一部を書き換えたりできます。これにより、古いコンテンツへのアクセスを新しいコンテンツに誘導したり、ユーザーフレンドリーな URL を提供したりできます。

__コンテンツの動的な生成または変更__:
* リクエストに基づいて、コンテンツの一部を動的に生成したり、既存のコンテンツを変更したりできます。これにより、パーソナライズされたコンテンツを提供したり、A/B テストを実施したりできます。

__認証と承認__:
* リクエストヘッダーに基づいて、ユーザーを認証したり、コンテンツへのアクセスを許可したりできます。これにより、会員限定コンテンツを提供したり、アクセス制限をかけたりできます。今回の設定はこれに該当します。


## 8-3. CloudFront Functionsの作成、発行
以下のような参考記事がありました。これを参考に、今回はhttpの基本的な認証の仕組みである基本認証を組み込みます。

https://qiita.com/narikkyo/items/274cfca3071709f6f4a5

下記のようなソースを用意します。ユーザ名とパスワードをコロンで区切ってBase64エンコードした文字列を用意すると以下のように簡単なもので最低限の機能は果たせます。

```JavaScript: CloudFront Functions に設定するJavaScriptソース
// 簡易型ベーシック認証
function handler(event) {
  var request = event.request;
  var headers = request.headers;

  // ユーザー名とパスワードをBase64エンコードした文字列
  // echo -n hoge:fuga | base64
  var authString = 'Basic aG9nZTpmdWdh';

  if (typeof headers.authorization === 'undefined' || 
             headers.authorization.value !== authString) {
    return {
      statusCode: 401,
      statusDescription: 'Unauthorized',
      headers: { 'www-authenticate': { value: 'Basic' } },
    };
  }

  return request;
}
```

今回は CloudFront Function で lt_haishin_auth と言う関数を作成。用意した上記ソースを貼付け、保存して発行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/33b59570-aaaf-1cbd-deb2-c9c73597da6b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/bbe0c78a-82b9-2171-57f2-f67eef718acb.png)

今回は簡単なスクリプトなのでテストは省略しましたが、もっと複雑なスクリプトの場合、発行する前にテスト機能で動作確認をしておいた方が良いと思います。※今回はテストについては割愛します。

## 8-4. ディストリビューションのビヘイビアで関数をディストリビューションに紐づける

作成した関数をディストリビューションの画面から紐づけ設定します。イベントタイプは「ビューアリクエスト」、関数は先程作成した lt_haishin_auth を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/5e2dadc6-8a2d-4c82-d95d-a522363d366e.png)

これで、CloudFrontを経由して動画にアクセスする際 パスワードを使ったBasic認証の手続きが行われるようになります。

# 9.動作確認
上記7.で書いた所定のURLへアクセスすると、下図のように基本認証のパスワード入力ダイアログが表示されます。
ここで CloudFront Functions で組み込んで置いたユーザ名とパスワード(今回例ではhoge/fuga)を入力すれば認証が通り、動画を閲覧できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/8d21e570-926b-526d-e59f-495400f9fdf1.png)

パスワードが誤っていた場合は401エラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2261111/d6245ae9-3e8f-fdd9-da7c-f2fc107fce7a.png)


# 10. CloudFrontのコストについて
　CloudFrontは毎月の転送量の無料枠が1TBまであります。今回のケースでは配信対象が社内限定であるため転送量はせいぜい数百GB程度であり、無料枠の範囲で利用できます。オリジンであるS3からCloudFrontへの転送は無料です。

# 11. 今回参考にした情報のURL
ご参考までに、今回参照した情報源を記載します。

## 11-1.非公式情報
- [【AWS】S3＋CloudFrontのOACでセキュアに静的ウェブサイトを公開する方法 #Security - Qiita](https://qiita.com/SoraoKosaka/items/8972d7f8969df28e0f70)

## 11-2.公式情報
- [署名付き URL と署名付き Cookie を使用したプライベートコンテンツを提供する - Amazon CloudFront](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html)
- [署名付き URL を使用したオブジェクトのアップロード - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/PresignedUrlUploadObject.html)
- [チュートリアル: Amazon S3、Amazon CloudFront、Amazon Route 53 を使用したオンデマンドストリーミング動画のホスティング。 - Amazon Simple Storage Service](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/tutorial-s3-cloudfront-route53-video-streaming.html)


以上です。ここまでお読みいただきありがとうございました。この記事がどなたかの参考になれば幸いです。