TwilioのPlay動詞にAmazon S3 Signature V4 Presigned URLを指定するとエラーになる

# はじめに
「TwilioのPlay動詞にAmazon S3 Signature V4 Presigned URLを指定するとエラーになることがあるので、注意が必要です」というお話です。
これは2019/05/09時点での話です。Twilioの仕様が変わる可能性はあります。

# 現象
Twilioで[Play動詞](https://jp.twilio.com/docs/voice/twiml/play)を使って、MP3ファイルを再生。MP3ファイルはAmazon S3に保存しており、有効期限をつけた署名付URLを指定。TwilMLは以下としていました。
※URLは、AWS SDK for PHP v3を使って動的に生成していました。

```xml:Gather-Play.xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Play>https://s3.ap-northeast-1.amazonaws.com/[BucketName]/[ObjectName]?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=********%2F********%2Fap-northeast-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20190509T100400Z&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Expires=1800&amp;X-Amz-Signature=****************************************************************</Play>
    <Gather numDigits="1" action="https://********">
        <Play>https://s3.ap-northeast-1.amazonaws.com/[BucketName]/[ObjectName]?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&amp;X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Credential=********%2F********%2Fap-northeast-1%2Fs3%2Faws4_request&amp;X-Amz-Date=20190509T100400Z&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Expires=1800&amp;X-Amz-Signature=****************************************************************</Play>
    </Gather>
</Response>
``` 

TwilMLに電話番号を紐づけ、架電すると、Twilio側でエラーが発生しました。ログは以下のような感じです。
![スクリーンショット 2019-05-09 22.51.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/7d128911-afc0-bc59-8bf0-7547322ab53c.png)
<img width="968" alt="スクリーンショット 2019-05-09 23.45.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110436/fd67216a-0aad-c462-c4ea-c432245efa06.png">

上記のリクエストインスペクターの２、3番目がPlayのMP3を取得するリクエストです。Play動詞のみは成功(200)、Gather内にあるPlay動詞は失敗(403)となっています。また、失敗している理由は「SignatureDoesNotMatch」でした。

# 原因
どうやら、Gather内にあるPlay動詞がMP3を取得するためのリクエストに、Twilio側がTwilio独自のリクエストパラメータを付与しているようです。
試しに、上記TwiMLで、Play動詞で指定するファイルを、別Webサーバー(Apache)に設置したファイルに変更し、別Webサーバーのアクセスログをみてみたところ、以下のようになっていました。

```text:access_log
"GET /filename.mp3 HTTP/1.1" 200 10694 "-" "TwilioProxy/1.1"
"GET /filename.mp3?Called=%2B8150********&ToState=&CallerCountry=JP&Direction=inbound&CalledVia=050********&CallerState=&ToZip=&CallSid=CA********************************&To=%2B8150********&CallerZip=&ToCountry=JP&ApiVersion=2010-04-01&CalledZip=&CalledCity=&CallStatus=in-progress&From=%2B8190********&AccountSid=AC********************************&CalledCountry=JP&CallerCity=&Caller=%2B8190********&FromCountry=JP&ToCity=&FromCity=&CalledState=&ForwardedFrom=050********&FromZip=&FromState= HTTP/1.1" 200 10694 "-" "TwilioProxy/1.1"
```
つまり、Gather内にあるPlay動詞がMP3ファイルにアクセスするとき、Twilio固有のパラメータを付与してアクセスしていることがわかります。
Amazon S3 Signature V4 Presigned URLに対して、パラメータを追加してアクセスしており、SignatureDoesNotMatchとなっています。
※例えば、Amazon S3 Signature V4 Presigned URLに適当なパラメータを追加してブラウザでアクセスしても同じエラーが発生します。

Play動詞のみなら問題ないのに、Gather内のPlay動詞になるとエラーになるあたりが、ハマりました。。。

# 解決方法
こちら、パラメータを追加されても問題のないURLを指定する必要があります。別サーバーに設置するなり、TwilioAssetsに設置するなり、追加されたパラメータに依存しないようなプロキシを作って通すなり。。。
なお、AWS S3でもSignature V2 Presigned URLであれば、この現象は発生しませんでした。ただ、Signature V2 Presigned URLは、2019/06/24以降利用できなくなりますので注意が必要です。詳しくは以下参照。
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/UsingAWSSDK.html#UsingAWSSDK-sig2-deprecation

# ちなみに
Twilioへ問い合わせをしたのですが、「仕様」とのことでした。
また、Gather内のPlay動詞だけでなく、Play動詞のみの場合でもリクエストにパラメータを付けることがあるとのことでした。

古いAWS CLI や AWS SDKを利用している場合、Signature V2 の可能性があり、2019/06/24以降、利用できなくなります。そのためアップデートするとSignature V4にはなりますが、本件に該当してしまうというケース、あるかもしれませんね。