AWS Elemental MediaStoreでCDN認証してオリジン保護してみた

メディア向けに最適化されたストレージサービスであるAWS Elemental MediaStoreで、特定のUser Agentの場合にのみアクセス許可するようコンテナポリシーを設定することでCDN認証を実現し、ライブ配信時のオリジン保護をしてみました。
2020.01.29

はじめに

清水です。先日、AWS Elemental MediaPackageでCDN認証を使ってオリジン保護ができるようになったアップデートをお届けしました。

AWS Media Servicesを使ってライブ配信を行う際は、要件によってオリジンを上記のジャストインタイムパッケージングサービスであるAWS Elemental MediaPackageか、メディア向けに最適化されたストレージサービスであるAWS Elemental MediaStoreかを選択することができます。ではMediaStoreでMediaPacakgeのようにCDN認証によるオリジン保護ができるのか、方法を確認し実際にMediaStoreとAmazon CloudFrontを用いてCDN認証によるオリジン保護をやってみたのでまとめてみます。

AWS Elemental MediaStoreでのCDN認証によるオリジン保護の方法

MediaStoreでのCDN認証によるオリジン保護の方法について、MediaPackageのようなCDN認証機能、というものはありませんでした。ですがMediaStoreのコンテナポリシーを利用することで、アクセスをCDNのみからというようにアクセス制御することは可能でした。イメージとしてはCloudFrontとS3連携時のOAI(Origin Access Identity)に似ているかと思います。具体的には以下となります。(なお、本エントリではCloudFrontをCDNとして扱いますが、別CDNでも同様の機能があれば実現は可能かと考えます。)

  • CloudFrontのカスタムオリジンヘッダによりUser Agentを個別の値に設定
  • MediaStoreではそのUser Agentの値であればアクセスを許可するようコンテナポリシーを設定

以下のフォーラムやAWS Media Blogのエントリを参考にしました。

では実際に設定していきます。(設定はMediaStore→CloudFrontの順で行いました。)

認証コードの作成

まずは認証コードとなる値を作成(決定)します。MediaPackageのCDN認証機能と同じく、version 4 UUIDを生成してこれを利用しました。

 $ python
Python 3.7.3 (default, Apr 23 2019, 11:32:00)
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import uuid
>>> str(uuid.uuid4())
'995c728c-d39b-4c1c-bab6-724a77795b9d'

この995c728c-d39b-4c1c-bab6-724a77795b9dを本エントリでは認証コードとして扱います。

MediaStoreのコンテナポリシーの設定

MediaStoreのコンテナポリシーで、認証コードをUser Agentとして持つアクセスに対し、読み取り権限を与えるよう設定します。まずはContainer作成から進めていきます。

Container作成後、詳細画面にてコンテナポリシーを設定します。Container作成直後のデフォルトのコンテナポリシーでは、以下のように作成したAWSアカウントにhttpsでのフルアクセスを許可する設定がされています。

{
	"Version": "2012-10-17",
	"Statement": [{
		"Sid": "MediaStoreFullAccess",
		"Action": [ "mediastore:*" ],
		"Principal": {"AWS" : "arn:aws:iam::123456789012:root"},
		"Effect": "Allow",
		"Resource": "arn:aws:mediastore:ap-northeast-1:123456789012:container/MediaStoreCDNAuthorizationTestContainer/*",
		"Condition": {
			"Bool": { "aws:SecureTransport": "true" }
		}
	}]
}

Edit policyで編集画面に進み、以下の内容を追加します。該当コンテナに対して、GetObject並びにDescribeObject権限を与える内容ですが、ConfitionでSecureTransportの他、UserAgentを指定しています。ここでCondition内はand条件となりますので、httpsアクセスでかつ、User Agentの値が認証コードであるアクセスのみが許可される、ということになります。

 {
      "Sid": "CloudFrontRead",
      "Effect": "Allow",
      "Principal": "*",
      "Action": [
        "mediastore:GetObject",
        "mediastore:DescribeObject"
      ],
      "Resource": "arn:aws:mediastore:[リージョン]:[AWSアカウントID]:container/[コンテナ名]/*",
      "Condition": {
        "StringEquals": {
          "aws:UserAgent": "[認証コード]"
        },
        "Bool": {
          "aws:SecureTransport": "true"
        }
      }
    }

実際に設定した画面がこちらです。

実際のコンテナポリシー内容は下記です。ハイライト部分が追加箇所になります。

{
  "Version" : "2012-10-17",
  "Statement" : [ {
    "Sid" : "MediaStoreFullAccess",
    "Effect" : "Allow",
    "Principal" : {
      "AWS" : "arn:aws:iam::123456789012:root"
    },
    "Action" : "mediastore:*",
    "Resource" : "arn:aws:mediastore:ap-northeast-1:123456789012:container/MediaStoreCDNAuthorizationTestContainer/*",
    "Condition" : {
      "Bool" : {
        "aws:SecureTransport" : "true"
      }
    }
  }, {
    "Sid" : "CloudFrontRead",
    "Effect" : "Allow",
    "Principal" : "*",
    "Action" : [ "mediastore:GetObject", "mediastore:DescribeObject" ],
    "Resource" : "arn:aws:mediastore:ap-northeast-1:123456789012:container/MediaStoreCDNAuthorizationTestContainer/*",
    "Condition" : {
      "StringEquals" : {
        "aws:UserAgent" : "995c728c-d39b-4c1c-bab6-724a77795b9d"
      },
      "Bool" : {
        "aws:SecureTransport" : "true"
      }
    }
  } ]
}

MediaStoreのコンテナポリシー設定は以上です。必要に応じてコンテナのCORSポリシーやライフサイクルポリシーを設定しておきましょう。

CloudFrontでUser Agentを個別の値に設定

続いてCloudFront側でカスタムオリジンヘッダを設定し、User Agentを個別の値(今回の「認証コード」)になるよう設定していきます。AWS Management ConsoleのCloudFrontのページ、ディストリビューション一覧画面から[Create Distribution]で進みます。Web distributionを作成していきましょう。

Create Distribution画面、Origin Domain NameでMediaStore Containersから先ほど作成、設定したContainerを選択します。

Origin IDなど項目が自動的に設定されますが、ここからいくつかの項目を変更していきます。まずはOrigin Protocol PolicyについてはHTTPS Onlyとしました。(MediaStoreのコンテナポリシーでawsSecureTransporttrueの場合にしか権限を付与していないためです。(HTTPSのみアクセス許可しています。)

続いてここが実際のCDN認証部分になります、Origin Custom Headersの箇所にて、Header NamerUser-AgentValueを認証コードである995c728c-d39b-4c1c-bab6-724a77795b9dと設定します。

Default Cache Behavior Settings、Distribution Settingsについては、必要に応じて設定を変更し、[Create Distribution]でディストリビューションを作成します。(CORS設定が必要な場合は、Cache Behavior SettingsでOrigin Headerを転送する設定を忘れないようにしましょう。)作成後、こちらも必要に応じて4xx、5xxエラーキャッシュ時間の変更などを行っておきます。

CDN認証されていることの確認

CloudFrontディストリビューション作成、利用可能になったら実際にCDN認証されていることを確認してみます。MediaLiveで出力先にMediaStoreを指定し、ライブ視聴が可能な状況としました。

まずは先ほど認証用の設定を行ったCloudFrontディストリビューション経由でアクセスしてみます。MediaStore上のm3u8ファイルのアドレス、Endpointは以下になります。

https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8

ドメイン名をCloudFrontディストリビューションのものに置き換えて、以下でアクセスしてみます。

https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls.m3u8

視聴確認にはVideoJS HTTP Streamingを使用しました。Video URLにCloudFrontドメイン名のURLを入力し、MimeType:application/x-mpegURLを確認して、[Load]ボタンを押します。問題なく再生できますね!

続いて、MediaStoreのエンドポイント(オリジン)に直接アクセスしてみましょう。以下のように403エラーとなってしまいました。オリジンへの直接アクセスが無効となっていることがわかります。

これらの内容を、curlコマンドを使って確認してみます。まずは認証設定を行ったCloudFrontからのアクセスです。m3u8ファイル、tsファイルとも、問題なくアクセスできていることが確認できます。

# トップレベルマニフェストファイル
[shimizu.toshiya@classmethod] [~]
 $ curl https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=3879928,AVERAGE-BANDWIDTH=3735600,CODECS="avc1.640029,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=29.970
hls_720p.m3u8

[shimizu.toshiya@classmethod] [~]
 $ curl -I https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls.m3u8
HTTP/2 200
content-type: application/vnd.apple.mpegurl
content-length: 198
x-amzn-requestid: LZNH35OJBX4YMDUSCL6CWXTAXNVU5HUEUHBCFDA6HNRJKRO5BOVADDCWZAXR66QMX2KHMLDEUC33HI7TKYJOGBY
last-modified: Wed, 29 Jan 2020 12:11:53 GMT
cache-control: max-age=3
etag: 11b698658a7ae847f72daa34f5839aa7464d4cb43dcf30a6b8edbb1d00c374ec
date: Wed, 29 Jan 2020 12:19:15 GMT
x-cache: Miss from cloudfront
via: 1.1 a5b5c04f6614f3f4049a1ce34ea76c9a.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C4
x-amz-cf-id: 1n3Haw-BJy1Ql4RRQKkv0Is7nl96NPXY0B08RbVBRE7cJmRG3D_YUA==


# ビットレートごとのマニフェストファイル(今回は1つのビットレートのみです)
[shimizu.toshiya@classmethod] [~]
 $ curl https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls_720p.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:72
#EXT-X-PROGRAM-DATE-TIME:2020-01-29T12:18:39.860Z
#EXTINF:6.00600,
hls_720p_00072.ts
#EXTINF:6.00600,
hls_720p_00073.ts
#EXTINF:6.00600,
hls_720p_00074.ts
#EXTINF:6.00600,
hls_720p_00075.ts
#EXTINF:6.00600,
hls_720p_00076.ts
#EXTINF:6.00600,
hls_720p_00077.ts
#EXTINF:6.00600,
hls_720p_00078.ts
#EXTINF:6.00600,
hls_720p_00079.ts
#EXTINF:6.00600,
hls_720p_00080.ts
#EXTINF:6.00600,
hls_720p_00081.ts

[shimizu.toshiya@classmethod] [~]
 $ curl -I https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls_720p.m3u8
HTTP/2 200
content-type: application/vnd.apple.mpegurl
content-length: 474
x-amzn-requestid: V54NWUDWTVDJQLYQKKM3K3PYQW4CKHCR3XIJLBYPI4FWH6NIKQPIXHIXULO2HVHYYGIMOQX3RILOESEYB7TTBWQ
last-modified: Wed, 29 Jan 2020 12:19:53 GMT
cache-control: max-age=3
etag: c2c605f332b3056b1f306014628a0dd173ea8867ec8cc58a7ef64f8eaabcd8e9
date: Wed, 29 Jan 2020 12:19:53 GMT
x-cache: Miss from cloudfront
via: 1.1 6b5ed72af06c392d3a24305474d937d8.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C4
x-amz-cf-id: Kv2P4TFXtpkHqEUx8KMX9WTPoi_AuCJQLLzzmudbLpNWC2j0C4IS2Q==


# tsファイル
[shimizu.toshiya@classmethod] [~]
 $ curl -I https://d3cxxxxxxxxxxx.cloudfront.net/mediastore-cdnauthtest/hls_720p_00081.ts
HTTP/2 200
content-type: video/MP2T
content-length: 2605868
x-amzn-requestid: WO7AJUEWJUGFQ3DZEIGQQE7EWFTOI3EZIMCNCTE6VTUXKET672NJTYOO2F27IIZY75TYRPXIRHQYQSNXFWU2PLA
last-modified: Wed, 29 Jan 2020 12:19:41 GMT
cache-control: max-age=21600
etag: e29e3f7ee59e3689d19e09fb272aa64e504b8182406d3f399979008a8b2005a7
date: Wed, 29 Jan 2020 12:20:11 GMT
x-cache: Miss from cloudfront
via: 1.1 db3d90fd7e6c6a16b47e88be13e9768c.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-C4
x-amz-cf-id: lt6tfCGcrqzUnzGata6QZaEDDer1q98943FMG0kWqaynRgQW8Pjm2g==

続いてMediaStoreのエンドポイントに直接アクセスしてみます。以下のように、403エラーでアクセスできないことがわかります。

# トップレベルマニフェストファイル
[shimizu.toshiya@classmethod] [~]
 $ curl https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8
{"Message":"Access Denied."}
[shimizu.toshiya@classmethod] [~]
 $ curl -I https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8
HTTP/1.1 403
x-amzn-RequestId: LLG6ZRXYN2NX734OONOINANHKWM4TIOCVGNWBLM4GVMUGPAO2EPCHHA3JOXXWYTFUIAN6HVIUZTXX6A45JOYK6Y
x-amzn-ErrorType: AccessDeniedException
Content-Type: application/x-amz-json-1.1
Transfer-Encoding: chunked
Date: Wed, 29 Jan 2020 12:20:33 GMT


# ビットレートごとのマニフェストファイル(今回は1つのビットレートのみです)
[shimizu.toshiya@classmethod] [~]
 $ curl https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p.m3u8
{"Message":"Access Denied."}
[shimizu.toshiya@classmethod] [~]
 $ curl -I https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p.m3u8
HTTP/1.1 403
x-amzn-RequestId: GGZRXOAP7ODLFPMFWQQLJXNUCAAKZHMCON3CBXZBXRY4TTUPXTWZ5NNA7UD4U72G46BXVWCEMTWTUH2XYB4BELQ
x-amzn-ErrorType: AccessDeniedException
Content-Type: application/x-amz-json-1.1
Transfer-Encoding: chunked
Date: Wed, 29 Jan 2020 12:20:51 GMT


# tsファイル(MediaStore上でファイルが存在していることは確認済みです)
[shimizu.toshiya@classmethod] [~]
 $ curl -I https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p_00081.ts
HTTP/1.1 403
x-amzn-RequestId: DUFVUXPG72B5G44EL3Q3UG4NL26H5VCLYID3GNJIW4TANNY5NDH73S3MG6UUPMFMEF6NZALCGUGNJDD75DKILUI
x-amzn-ErrorType: AccessDeniedException
Content-Type: application/x-amz-json-1.1
Transfer-Encoding: chunked
Date: Wed, 29 Jan 2020 12:21:10 GMT

続いてCloudFrontディストリビューションに設定したように、認証情報となるUser Agentヘッダを個別の情報(今回なら認証コード「995c728c-d39b-4c1c-bab6-724a77795b9d」としてアクセスを確認してみましょう。curlコマンドにオプションで、ヘッダ名: User-Agent、ヘッダの値: 995c728c-d39b-4c1c-bab6-724a77795b9d(認証コード)を付与してアクセスしてみます。結果としては以下の通り、問題なくアクセスできることが確認できます。

# トップレベルマニフェストファイル
[shimizu.toshiya@classmethod] [~]
 $ curl -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=3879928,AVERAGE-BANDWIDTH=3735600,CODECS="avc1.640029,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=29.970
hls_720p.m3u8

[shimizu.toshiya@classmethod] [~]
 $ curl -I -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls.m3u8
HTTP/1.1 200
x-amzn-RequestId: B3UHI2GYMCCAIZNB65HLTPUPFEW3YEXX2YNRUBCL2BJJ62T577UL66VLLHOIKLLCSAF32ASO4UO77LQSDPS3RPQ
Last-Modified: Wed, 29 Jan 2020 12:11:53 GMT
Cache-Control: max-age=3
ETag: 11b698658a7ae847f72daa34f5839aa7464d4cb43dcf30a6b8edbb1d00c374ec
Content-Type: application/vnd.apple.mpegurl
Content-Length: 198
Date: Wed, 29 Jan 2020 12:22:45 GMT


# ビットレートごとのマニフェストファイル(今回は1つのビットレートのみです)
[shimizu.toshiya@classmethod] [~]
 $ curl -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:7
#EXT-X-MEDIA-SEQUENCE:104
#EXT-X-PROGRAM-DATE-TIME:2020-01-29T12:21:52.052Z
#EXTINF:6.00600,
hls_720p_00104.ts
#EXTINF:6.00600,
hls_720p_00105.ts
#EXTINF:6.00600,
hls_720p_00106.ts
#EXTINF:6.00600,
hls_720p_00107.ts
#EXTINF:6.00600,
hls_720p_00108.ts
#EXTINF:6.00600,
hls_720p_00109.ts
#EXTINF:6.00600,
hls_720p_00110.ts
#EXTINF:6.00600,
hls_720p_00111.ts
#EXTINF:6.00600,
hls_720p_00112.ts
#EXTINF:6.00600,
hls_720p_00113.ts

[shimizu.toshiya@classmethod] [~]
 $ curl -I -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p.m3u8
HTTP/1.1 200
x-amzn-RequestId: UUOOWL3OZ5WCCSSWG7FIMOCBVEWNBEZOBLPELW2AWF67WTH6NWGEEIU5KZSMNIP6UUTLCDA7U24PUJI4GMYH3TQ
Last-Modified: Wed, 29 Jan 2020 12:22:53 GMT
Cache-Control: max-age=3
ETag: 2b01e6bebba3b0ba31cd8e98b16d0f48af2481707fff0e5693a938400e425049
Content-Type: application/vnd.apple.mpegurl
Content-Length: 475
Date: Wed, 29 Jan 2020 12:22:57 GMT


# tsファイル
[shimizu.toshiya@classmethod] [~]
 $ curl -I -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p_00113.ts
HTTP/1.1 200
x-amzn-RequestId: ZN6IBF6LNUKCOJINVRGMYEY3T5455GWOAXH53MG6B7HTXKN2KTYUNNOJPJNSI7UR6LIRK3IGBUPK6VM5TASOQBY
Last-Modified: Wed, 29 Jan 2020 12:22:53 GMT
Cache-Control: max-age=21600
ETag: 94852ef8e43d61c2feb533e4971283b643551e1817ee85704c63a8476153101e
Content-Type: video/MP2T
Content-Length: 2632188
Date: Wed, 29 Jan 2020 12:23:32 GMT


[shimizu.toshiya@classmethod] [~]
 $ curl -I -H 'User-Agent:995c728c-d39b-4c1c-bab6-724a77795b9d' https://ckexxxxxxxxxxx.data.mediastore.ap-northeast-1.amazonaws.com/mediastore-cdnauthtest/hls_720p_00113.ts
HTTP/1.1 200
x-amzn-RequestId: 6FLA22VAX57DHMCHOENOTJGZZXVKAOCWAP5AIGC5ROM4H3FH3GBXLEFIPMI7Z73YQZ4WVVBTCEN34EEP2KMO73Q
Last-Modified: Wed, 29 Jan 2020 12:22:53 GMT
Cache-Control: max-age=21600
ETag: 94852ef8e43d61c2feb533e4971283b643551e1817ee85704c63a8476153101e
Content-Type: video/MP2T
Content-Length: 2632188
Date: Wed, 29 Jan 2020 12:23:50 GMT

まとめ

メディア向けに最適化されたストレージサービスでありAmazon S3よりも整合性の強いストレージでもあるAWS Elemental MediaStoreで、特定のUser Agentの場合にのみアクセス許可するようコンテナポリシーを設定することでCDN認証を実現し、ライブ配信時のオリジン保護をしてみました。AWS Elemental MediaPackageでのCDN認証と比較すると、やはりいち機能として設定するのではなく、コンテナポリシーの中で条件として設定するのが大きく異なるポイントかと思います。細かく言えば、AWS Secrets Managerを使うか使わないかもポイントですね。ただCDNとなるAmazon CloudFront側は、ヘッダさえ違えど、設定内容は同様かと考えます。

ということで先日のアップデート、AWS Elemental MediaPackageでのCDN認証によるオリジン保護はAWS Elemental MediaStoreでも同様のことができることがわかりました。ライブ配信時のオリジン選定について、オリジン保護の観点ではどちらも利用することができますので、その他の要件にあわせてどちらをオリジンとして使うか検討してみましょう。