Pythonを使ってCloudFrontのサイン付きURLを作る

# 前提

当方では、Ploneを用いて動画(Video)をストリーミング再生させるプロダクトを公開しています。その際に、Ploneのユーザ情報(ログインユーザ情報)を元に、閲覧の制限をさせたいと考えました。ストリーミング動画のアクセス管理は非常に手間のかかることですが、AWS CloudFrontのサイン付きURL(signed url)機能を使うことで実現できます。

Ploneとの組合せで使用する際には、 [c2.app.streamingaws](https://pypi.python.org/pypi/c2.app.streamingaws) を用いることで、ソースコードを書かずにセキュアに動画配信が可能です。

ここでは、 Python + boto を使って、サイン付きURLの生成方法及びVideoJSを用いたHTMLの書き方をまとめます。

# 必要な条件・情報

- Python 2.7 or 3.4 (botoに依存/他のバージョンでも可能かもしれないが試していない) (私は 2.7系のみで試した)
- boto (使用したのは、boto 2.36.0 (現状の最終バージョン) / 以前のバージョンでも機能すると思う)
- AWS アカウント (IAMユーザでも可能 / 適切な権限付は必要)
- CloudFront keyペア
- S3 及び CloudFront の設定 (別のエントリーを見て下さい)
- URL有効時間の決定 (ストリーミング動画再生の場合、動画の時間以上を設定する必要がある)


ここまで準備、決まったら実装するのみ。


# サイン付きURLの生成

```create_url.py
import time
from boto.cloudfront import CloudFrontConnection
from boto.cloudfront.distribution import Distribution

access_key_id = "YOUR_KEY_ID" # AWS (or IAM) アカウントのID
secret_access_key = "YOUR_SECRET_KEY" # AWS (or IAM) アカウントのシークレットキー
cloudfront_key_id = "CLOUD_FRONT_KEY_ID" # CloudFront のID
cloudfront_private_key_str = """XXXXXXXXXXXXX
""" # CloudFront の 秘密鍵 (-----BEGIN RSA PRIVATE KEY-----を含めて)

expire_time = int(time.time() + 3600) # 有効時間を3600秒 (1時間) に設定

conn = CloudFrontConnection(access_key_id, secret_access_key)
distribution = Distribution(connection=conn)

def get_signed_url(url, policy_url):
    signed_url = distribution.create_signed_url(url=url,
                            keypair_id=cloudfront_key_id,
                            expire_time=expire_time,
                            policy_url=policy_url,
                            private_key_string=cloudfront_private_key_str)
    return signed_url

if __name__ == '__main__:
    url = ""
    policy_url = ""
    print get_signed_url(url, policy_url)
```

# VideoJSでの表示

video.js を用いる場合、以下のように `video` タグ や `source` タグ を記述します。video.js でのHTMLの記述方法は以下のエントリーを参照してください。

- [iOS向けにAWS S3 + CloudFrontで動画ストリーム](http://qiita.com/terapyon/items/e4925c15c7ee58778f92)


## 画像の場合

```create_signed_url_for_image.py
form .create_url import get_signed_url
domain = "xxxx.cloudfront.net" # ClundFront の HTTP(S) の Domain
base_path = "video/" # 画像が保管されているS3上のPATH
img_id = "thumb.png" # 画像のファイル名
url = "https://" + domain + "/" + base_path + img_id
policy_url = "https://" + domain + "/" + base_path + "*"
signed_url = get_signed_url(url, policy_url)
```

```video.html
<video id="video_1" class="video-js vjs-default-skin"
      controls preload="auto" width="640" height="360"
      poster="${signed_url}" <!-- 上記のsigned_urlを動的にはめる -->
      data-setup='{}'>
    <source src="" type="" />
</video>
```


## RTMPの場合

```create_signed_url_for_rtmp.py
form .create_url import get_signed_url
domain = "xxxx2.cloudfront.net" # ClundFront の RTMP の Domain
base_path = "video/" # 動画が保管されているS3上のPATH
video_id = "video.mp3" # 動画のファイル名
url = "rtmp://" + domain + "/cfx/st/&mp4:" + base_path + video_id
policy_path = "base_path + video_id # RTMPの場合はドメイン等を含まない
signed_url = get_signed_url(url, policy_path)
```

`policy_path` には、 `rtmp://を含むドメイン` 等は含めない。 

```video.html
<source src="${signed_url}"
        type='rtmp/mp4' />
```


## HLSの場合

HLSの場合は、 `m3u8` というメタデータファイルをCloudFront経由で取得し、メタデータファイルを動的に書きなおしたものを、HTTP(S)通信で渡す必要があります。

まずは、この `m3u8` ファイルを自身のサーバにリクエストが来るようにHTMLを記載します。


```video.html
<source src="http://MY_SERVER_DOMAIN/${signed_url}"
        type="application/x-mpegURL" />
```


次に、 `m3u8` を動的に生成する必要があります。

m3u8ファイルには、分割された動画ファイルのURL(m3u8と同じ階層ならファイル名のみ)が列挙されています。それらの動画ファイルのURLを、サイン付きURLに書き換えます。
m3u8ファイルはシンプルなテキストファイルなので、書き換えは容易です。以下のようにファイルを書き換え、何かしらで自身のサーバからHTTP(S)通信で提供します。

m3u8ファイルを取得するための `signed_url` の取得。

```create_signed_url_for_hls.py
form .create_url import get_signed_url
domain = "xxxx.cloudfront.net" # ClundFront の HTTP(S) の Domain
base_path = "video/" # 動画が保管されているS3上のPATH
meta_id = "index.m3u8" # 動画メタデータのファイル名
url = "https://" + domain + "/" + base_path + meta_id
policy_url = "https://" + domain + "/" + base_path + "*"
signed_url = get_signed_url(url, policy_url)
```

取得した、  `signed_url` から、m3u8ファイルデータを取得し、以下の様なスクリプトで、データ内部を書き換え、HTTP(S)通信で提供します。

```zope_server.py
import urllib2

def _adding_param_for_m3u8(data, base_url, signed_param):
    for li in data.split("\n"):
        if not li.strip():
            yield li
        elif li.startswith("#"):
            yield li
        else:
            yield base_url + "/" + li + "?" + signed_param

class View:
    def render(self, signed_url):
        """
        """
        sp_signed_url = signed_url.split("?", 1)
        if len(sp_signed_url) < 2:
            return None
        base_url = sp_signed_url[0].rsplit("/", 1)[0]
        signed_param = sp_signed_url[1]
        m3u8_data = urllib2.urlopen(signed_url)
        data_list = _adding_param_for_m3u8(m3u8_data.read(),
                                           base_url, signed_param)
        m3u8_data.close()
        self.request.RESPONSE.setHeader('Content-Type', 'application/x-mpegURL')
        return "\n".join(data_list)
```



# 関連コンテンツ

- [AWS S3 + CloudFront で動画ストリーム](http://qiita.com/terapyon/items/b77ac981da790662e247)
- [iOS向けにAWS S3 + CloudFrontで動画ストリーム](http://qiita.com/terapyon/items/e4925c15c7ee58778f92)
- [AWS CloudFrontでプライベートコンテンツを配信](http://qiita.com/terapyon/items/ff2dd73bd479fbd44e82)
- [iOS対応ストリーミングをプライベート(制限付き)に対応](http://qiita.com/terapyon/items/fadecf13ed1d11dcd34d)


# 最後に

このエントリーは、Ploneに実装したプロダクトから、サイン付きURLを作る部分を抜き出し、VideoJSで表示させる部分の解説をしました。

部分的に記載ミスが残っている可能性があります。ミスについてはご指摘いただけたら幸いです。

著者関連Blog記事とあわせてお読みください。

[AWS CloudFront でプライベートコンテンツ配信](https://www.cmscom.jp/blog/hz2f2t)