Amazon IVSのプライベートチャンネル用tokenを手元のPython環境で作成してみた

Amazon Interactive Video Serviceのプライベートチャンネルを使ったライブ配信の再生に必要なJSON Web Token(JWT)をPythonの対話型インタプリタ環境で作成してみました。
2020.09.28

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。先日のアップデートでプライベートチャンネル機能が追加されたAmazon Interactive Video Service(Amazon IVS)、以下の公式ブログやワークショップ資料などで具体的な手順が公開されていますね。

動作の概要としては動画プレイヤー(Amazon IVS Player)からPlayback URLへのアクセス時にクエリ文字列でtokenを付与する必要がある、というところまでは把握できたのですが、別途AWS LambdaやAmazon API Gatewayまわりを準備する部分が個人的にしっくりきていませんでした。LambdaやAPI Gatewayを使わずに、手元の環境(ふだん使っているmacOSやLinuxサーバ上)でこのJSON Web Token(JWT)を生成できないものかと思い、やってみたのでまとめてみます。具体的にはPythonの対話型インタプリタ上で必要な情報を入力しながらJWTを生成しました。

実際にこの手元での環境、つまりLambdaなどではない環境でもJWTの生成は可能で、これを使ったAmazon IVSのプライベートチャンネルの再生ももちろん可能でした。ただ実際にサービスに組み込む場合などには、何らかの認証機能などと組み合わせてJWTを生成することになり、その際にLambda+API Gatewayな環境を想定しているのかなぁと推測しています。

IVSのPlayback keyとプライベートチャンネルの準備

まずはIVSでPlayback keyならびにプライベートチャンネルを準備します。

Playback keyの作成

Playback keyをAWSマネジメントコンソールから作成します。「Playback keys」のページにて[Create playback key]ボタンを押下し、ダイアログでキー名称を入力します。

[Create]ボタンでkeyを作成します。これでキーペアが作成され、公開鍵についてはAWSでリソースとして管理されます。秘密鍵についてはブラウザからダウンロードできるので、保存しておきます。なおダウンロードできるのはこのタイミングのみですので注意しましょう。ダウンロードした秘密鍵はivs-playback-key-1.pemという名称で保存しておきました。

プライベートチャンネルの作成

続いてIVSでプライベートチャンネルを作成します。マネジメントコンソールのIVSのページ、[Create Channel]ボタンから進みSetupのChannel configurationでCustom configurationを選択、Playback authorizationの項目でEnable toke-authorization requirement for video playbackを有効にします。

チャンネル作成後、Playback authorizationの項目が有効になっていること、またLive streamのプレビュー部分が利用できないこと(プライベートチャンネルのため)を確認しておきます。

Pythonの対話型インタプリタ環境でのJWTの生成

続いてPython環境からJWTを生成していきます。

Python環境の準備

今回環境としてはAmazon Linux 2でEC2インスタンスを起動し、Python3環境をyumでインストールしていきました。

[ec2-user@ip-10-82-21-161 ~]$ sudo yum install python3 -y

以下のように、python3コマンドならびにpip3コマンドが使用できる環境となりました。

[ec2-user@ip-10-82-21-161 ~]$ which python3
/usr/bin/python3
[ec2-user@ip-10-82-21-161 ~]$ python3 --version
Python 3.7.9
[ec2-user@ip-10-82-21-161 ~]$ which pip3
/usr/bin/pip3
[ec2-user@ip-10-82-21-161 ~]$ pip3 --version
pip 9.0.3 from /usr/lib/python3.7/site-packages (python 3.7)

続いてPython環境でJWTを扱うライブラリとしてpyjwt、そしてES384で署名の際に使用するcryptographyをpipでインストールしておきます。Python環境でJWTを扱うライブラリとしては他にもpython-joseなどいくつかあるかと思いますが、今回は公式ドキュメントからも参照されているページjwt.ioにてPythonでFilterした際に一番最初に出てきたことからpyjwtを選択しています。また、アルゴリズムとしてES384を指定して署名しようとしたところ、「NotImplementedError: Algorithm 'ES384' could not be found. Do you have cryptography installed?」というエラーが現れ、こちらのページを参考にcryptographyもインストールした、という流れです。なおrootでのpip installは推奨されていません。(その旨、警告も現れます。)実際の環境に適したかたちで実行ください。

[ec2-user@ip-10-82-21-161 ~]$ sudo pip3 install pyjwt
[ec2-user@ip-10-82-21-161 ~]$ sudo pip3 install cryptography

pip listすると以下の状態です。

[ec2-user@ip-10-82-21-161 ~]$ pip3 list
Package      Version
------------ -------
cffi         1.14.3
cryptography 3.1.1
pip          9.0.3
pycparser    2.20
PyJWT        1.7.1
setuptools   38.4.0
six          1.15.0

JWT生成時の情報の確認

続いて、JWTを生成する際の各情報をあらかじめ確認しておきます。IVS公式ドキュメントの以下ページを参考にしましょう。

またpyjwtの使い方については以下ページを参考にします。

IVS公式ドキュメントから、JWTのフィールドにはheaderpayloadそしてsignatureの3項目があることがわかります。まずheaderについてはtoke typeがJWTであることを押さえておきつつ、署名アルゴリズムとしてES384を使うことを確認しておきます。pyjwt使用の際にはalgorithm='ES384'の指定だけで良さそうですね。

続いてpayload部分です。ドキュメントに以下の例が記載されています。

{
    "aws:channel-arn": <channel_arn>,
    "aws:access-control-allow-origin": "<your-website>",
    "exp": <unix timestamp>
}

"aws:channel-arn"については、IVS ChannelのARNですね、マネジメントコンソールなどから、arn:aws:ivs:us-west-2:123456789012:channel/XXXXXXXXXXXXな形式のARNを確認しておきましょう。

"aws:access-control-allow-origin"はクロスオリジンリクエストを許可するかどうか、CORS設定を行うことができます。今回はAmazon IVS Player含めたhtmlファイルをS3でホスティグした環境で再生確認をします。S3バケット名をmy-s3-bucket、リージョンを東京リージョンとして、"https://my-s3-bucket.s3-ap-northeast-1.amazonaws.com"を指定することにします。指定はドメイン名のみではなく、プロトコル部分https://も必要ですので注意しましょう。(私ははじめ、指定し忘れてハマりかけました。)

最後のexpはtokenの有効期限になります。Unixタイプスタンプで指定します。

以上3つの項目をpayloadとしてJWT生成時、pyjwtではjwt.encode呼び出し時に指定します。またこの際に秘密鍵についても必要となります。この秘密鍵についてはPython環境から読み込める場所に配置しておきます。(今回であればAmazon Linux 2が稼働しているEC2インスタンス上にコピーしておきました。)

Python対話型インタプリタ上でのJWT作成

では実際にJWTを作成していきます。Python対話型インタプリタを起動し、datetimejwtimportします。

[ec2-user@ip-10-82-21-161 ~]$ python3
Python 3.7.9 (default, Aug 27 2020, 21:59:41)
[GCC 7.3.1 20180712 (Red Hat 7.3.1-9)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import datetime
>>> import jwt
>>>

tokenの有効期限は3日後とします。以下で最終的にUnixタイムスタンプ形式に変換できることを確認しておきます。

>>> datetime.datetime.utcnow() + 3*(datetime.timedelta(days=1))
datetime.datetime(2020, 9, 30, 8, 26, 10, 171448)
>>> expires = datetime.datetime.utcnow() + 3*(datetime.timedelta(days=1))
>>> expires
datetime.datetime(2020, 9, 30, 8, 26, 51, 851024)
>>> int(expires.timestamp())
1601454411
>>>

これを用いてpayloadを指定します。

>>> payload = {
... "aws:channel-arn": "arn:aws:ivs:us-west-2:123456789012:channel/XXXXXXXXXXXX",
... "aws:access-control-allow-origin": "https://my-s3-bucket.s3-ap-northeast-1.amazonaws.com",
... "exp": int(expires.timestamp())
... }
>>> payload
{'aws:channel-arn': 'arn:aws:ivs:us-west-2:123456789012:channel/XXXXXXXXXXXX', 'aws:access-control-allow-origin': 'https://my-s3-bucket.s3-ap-northeast-1.amazonaws.com', 'exp': 1601454411}
>>>

秘密鍵についても読み込んでおきます。

>>> secret = open('/home/ec2-user/ivs-playback-key-1.pem', 'r').read()
>>> secret
'-----BEGIN EC PRIVATE KEY-----\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n-----END EC PRIVATE KEY-----\n'
>>>

そして、jwt.encodeを使ってtokenを生成します。

>>> encoded_jwt = jwt.encode(payload, secret, algorithm='ES384')
>>> encoded_jwt
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXImV4cCI6MTYwMTQ1NDQxMX0.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
>>>

この変数encoded_jwtに格納されている情報、上記の表示で言えば`で囲まれた部分がtokenになります。では続いて、このtokenを使って実際にプライベートチャンネルの再生を行ってみます。

IVSのプライベートチャンネルでライブ配信して再生確認

tokenが作成できたので、これを使ってIVSのプライベートチャンネルのライブ視聴ができるか確認してみます。再生ページとして以下のhtmlを準備し、JWT生成次のpayloadでCORS設定をしたS3バケットでホスティングします。

ivs-private-channel.html

<html>
  <head>
    <title>ivs-private-channel</title>
    <style>
      video {
          height: 100%;
          width: 100%;
          left: 0;
          top: 0;
          position: fixed;
      }
    </style>
  </head>
  <body>
    <video id="video-player" playsinline controls></video>
    <script src="https://player.live-video.net/1.0.0/amazon-ivs-player.min.js"></script>
    <script>
      var PLAYBACK_URL = "https://XXXXXXXXXXXX.us-west-2.playback.live-video.net/api/video/v1/us-west-2.123456789012.channel.XXXXXXXXXXXX.m3u8";
      var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXImV4cCI6MTYwMTQ1NDQxMX0.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
      if (IVSPlayer.isPlayerSupported) {
          const player = IVSPlayer.create();
          player.attachHTMLVideoElement(document.getElementById('video-player'));
          player.load(PLAYBACK_URL + "?token=" + token);
          player.play();
      }
    </script>
  </body>
</html>

Streaming SoftwareにIVSチャンネルの設定を行い、映像を打ち上げてライブ配信を開始します。今回Streaming SoftwareはiPhone 6s上でZixi ONAIRを使用しました。

マネジメントコンソールでライブ配信が開始されている状態であることを確認します。プライベートチャンネルなのでプレビューは参照できません。

再生ページを確認してみます。問題なくライブ配信が視聴できますね!

まとめ

Amazon Interactive Video Service(Amazon IVS)のプライベートチャンネルで再生に必要なtokenをPythonの対話型インタプリタ環境で作成してみました。JWTの作成方法自体はAWS環境固有のものではなく、一般的な方法が使えるかと思いますので、今回確認してみたPython以外でも様々な環境でJWTが作成できるかと思います。ただ冒頭でも述べましたが、実際にサービスに組み込み、ユーザ認証からのtoken作成などではサーバレス環境にしたり、そもそもプログラマブルに作成することが前提かと考えます。とはいえ、さくっと検証してみたい、なんてときにはこのように手元のPythonの対話型インタプリタで作成してしまう、なんてこともありなのではないでしょうか。