JavaでCloudFrontの署名付きURLを作成してS3にマルチパートアップロードする

JavaでCloudFrontの署名付きURLを使用してS3にマルチパートアップロードをする実装を行う機会があったので紹介します。

CloudFrontもS3もAWSのサービスのひとつで、CloudFront・S3それぞれに署名付きURLの機能がありますが、今回は開発の環境仕様上、CloudFrontの署名付きURLを使用しました。
マルチパートアップロードとはS3の機能で、サイズの大きいファイルを分割してアップロードし、最後にS3でファイルを統合して保存してくれるというものです。

CloudFrontの署名付きURLを使用してS3にマルチパートアップロードをするという組み合わせについての情報がAWS公式にもネット上にも皆無で実装に苦労しました。

# S3 マルチパートアップロード
まず処理のベースとなるのがS3のマルチパートアップロードです。
この機能単体であればAWS公式にも情報が詳細に記載されていて、それほど難しい事ではありません。
以下の手順を追ってS3にリクエストを実行する必要があります。

**1. マルチパートアップロードの開始：CreateMultipartUpload
2. 各パートのアップロード：UploadPart
3. マルチパートアップロードの完了：CompleteMultipartUpload**

今回は１と３をJavaで実装し、２をクライアントからS3にリクエストという仕様です。
そして２がS3のUploadPartでもあり、CloudFrontの署名付きURLとしても実行する所です。
２の手順を具体的に書くと、Javaで作成したCloudFrontの署名付きURLをクライアントに返却し、クライアントで署名付きURLを分割ファイルを添付してS3にリクエストするという流れです。

# 1. マルチパートアップロードの開始：CreateMultipartUpload
ここはAWS公式サイトに詳細が記載されているので特に難しい所ではありません。
Javaなら`AmazonS3Client.initiateMultipartUpload()`メソッドを使用してマルチパートアップロードを開始します。
引数もリファレンスに載っているものを渡せばOKです。
Rest APIのURLも参考までに載せます。

```:Java
InitiateMultipartUploadResult result = s3client.initiateMultipartUpload(initiateMultipartUploadRequest);
```
```:Rest API
POST https://<ディストリビューション名>.cloudfront.net/<オブジェクトキー名>?uploads
```
（Rest APIの<ディストリビューション名>は代替ドメイン名でもいけました）

Javaの場合、戻り値の`InitiateMultipartUploadResult`に`uploadId`が含まれていて、これを後続処理の２、３でアップロード単位を識別する為に使用します。

# 2. 各パートのアップロード：UploadPart
ここが今回の実装の肝でした。

### 通常のS3のマルチパートアップロードのUploadPart
まず、CloudFrontの署名付きURLを使わない通常のS3のマルチパートアップロードのUploadPartについてはAWS公式に詳細があるので、その通り実行すればOKです。
RestAPIだと以下の通りです。

```:Rest API
PUT https://<バケット名>.s3.amazonaws.com/<オブジェクトキー名>?partNumber=<マルチパートの番号>&uploadId=<1のuploadId>　body:<アップロードファイル>
```

JavaでのUploadPartのサンプルもAWS公式に載っています。
が、ファイルアップロードはクライアントから行う事が多そうなのでRest APIの需要が多いかもしれません。

### CloudFrontの署名付きURLを使用したUploadPartの実行

では本題です。
今回、CloudFrontの署名付きURLを使用したUploadPartはクライアントから行うので、その為のURLを作成する必要があります。

結論から言うと以下のURL構文になります。

```:Rest API
PUT https://<ディストリビューション名>.cloudfront.net/<オブジェクトキー名>?partNumber=<マルチパートの番号>&uploadId=<1のuploadId>&Policy=<ポリシー>&Signature=<署名>&Key-Pair-Id=<キーペアID>　body:<アップロードファイル>
```

通常のS3のマルチパートアップロードのUploadPartのURLと比べると、ホスト部がCloudFrontのものになっていて、それにUploadPartの引数のpartNumberやuploadIdが付加されていて、更にPolicy、Signature、Key-Pair-Id等のCloudFrontの署名付きURLとしての引数が付加されている事がわかると思います。
ベースはCloudFrontの署名付きURLで、それにS3のUploadPartとしての情報を付加している感じでしょうか。
<ディストリビューション名>が代替ドメイン名でもいけるのは、CreateMultipartUploadと同じです。

では、各引数の詳細を説明していきます。

#### Policy=<ポリシー>
CloudFrontのカスタムポリシーを作成します。
AWS公式の情報は以下です。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html

カスタムポリシーを作成するにあたって、まずJSONポリシーステートメントを作成します。
その為にベースURLというものが必要で、今回の場合の例で言うと以下のようなものになります。

```:カスタムポリシーのベースURL
https://<ディストリビューション名>.cloudfront.net/<オブジェクトキー名>?partNumber=<マルチパートの番号>&uploadId=<1のuploadId>
```

あとはAWS公式の手順に沿ってポリシーを作成します。
Base64で暗号化を行うのですが、この辺りの処理はJavaの場合、AWS SDK for Javaで提供されているメソッドで行う事ができ、以下のようにAWS公式でも情報が提供されています。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/CFPrivateDistJavaDevelopment.html

公式にも記載がある通り、バージョン１、２共にメソッドが提供されています。
（今回の私の場合は、開発仕様がバージョン１だったため`CloudFrontUrlSigner.getSignedURLWithCustomPolicy()`メソッドを使用しました）

#### Signature=<署名>
署名作成手順については、前述のポリシー作成で紹介したAWS公式ページに、ポリシー作成に続いて詳細が記載されています。
と言うのも、署名もJSONポリシーステートメントから作成するので、元はポリシーと同じなのです。
違いは、ポリシーはJSONポリシーステートメントをBase64して暗号化したもので、署名はJSONポリシーステートメントをハッシュ化した上、Base64で暗号化したものという所です。

署名作成もJavaの場合、AWS SDK for Javaでメソッドが提供されています。

・・・というか、`CloudFrontUrlSigner.getSignedURLWithCustomPolicy()`の中でポリシー作成と署名作成、更にはホスト部が付いたURLまで作成してくれます。
（私の場合はバージョン１を使用していてバージョン２は試していませんが、恐らくバージョン２でも同様のメソッドがあると思われます）
具体的に言うと、以下のURLまでメソッドが作成してくれます。

```:Java
https://<ディストリビューション名>.cloudfront.net/<オブジェクトキー名>?Policy=<ポリシー>&Signature=<署名>&Key-Pair-Id=<キーペアID>
```

`getSignedURLWithCustomPolicy`というメソッド名の通り、CloudFrontの署名付きURLを取得するメソッドだから、ですね。
このURLにpartNumberやuploadIdの引数を付加すると、前述したS3マルチパートアップロード版CloudFront署名付きURLの完成となります。

#### Key-Pair-Id=<キーペアID>
最後にキーペアIDです。
これは以下のAWS公式に載っている手順を行います。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html

手順を進めるとパブリックキーIDというIDが付与されるので、このパブリックキーIDをKey-Pair-Idの値として設定します。

以上の情報を組み立ててS3マルチパートアップロード用CloudFront署名付きURLを作成します。
これを分割ファイル数分、作成し、各URLに分割ファイルを添付してS3にリクエストします。

## 3. マルチパートアップロードの完了：CompleteMultipartUpload

最後にマルチパートアップロードを完了します。
これによって分割ファイルが統合され、元の１ファイルとしてS3に保存されます。

AWS公式の詳細情報もあり、CreateMultipartUploadと同じく単純にリクエストすればいいだけなので特に難しい所はありません。
が、各パートの署名付きURLのレスポンスでMD5が返却されるので、bodyでそれら全パート分のMD5を設定して送るという点には留意する必要があります。

## その他、CloudFrontやS3の設定等
CloudFrontの署名付きURLを使用してS3にマルチパートアップロードを行うにあたっての前提となるS3やCloudFrontの環境設定について、参考までに記載しておきます。


#### S3
* アップロード先のS3バケットをCloudFrontディストリビューションのS3オリジン[^1]として構成し、当該オリジンへのアクセスにOAC[^2]を使用するよう構成。
#### CloudFront
* ビヘイビアの対象のパスパターンで、許可対象のHTTPメソッドにPUT/POSTを追加し、リクエストの転送先として上記のS3オリジンを指定。
* ビヘイビアの対象のパスパターンで、上記のS3オリジンに対して元のリクエストのクエリ文字列を転送するよう構成。
* ビヘイビアの対象のパスパターンで、当該パスパターンに合致するコンテンツへのアクセスをCloudFrontの署名付きURL/Cookie[^3]で保護するよう設定。
    
## 最後に
以上がJavaでCloudFrontの署名付きURLを使用してS3にマルチパートアップロードを行う手順の説明でした。

[^1]: CloudFront ディストリビューションでさまざまなオリジンを使用する - 標準的な Amazon S3 バケットを使用する
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html#concept_S3Origin

[^2]: Amazon Simple Storage Service オリジンへのアクセスを制限する
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html

[^3]: 署名付き URL と署名付き Cookie を使用したプライベートコンテンツを提供する
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html