AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

署名付きURLはよくあるパターンですが、署名付きCookieでコンテンツ配信はあまり使用する機会がなかったので今回調べてみました。

CloudFront設定

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツをコンテンツを配信する方法

「Create Distribution」をクリックします。

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツをコンテンツを配信する方法

Webの「Get Started」をクリックします。

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

「Origin Domain Name」でS3バケットと紐づけます。

「Create a New Identity」とすることでOAI(Origin Access Identity)が自動作成されます。この際に「Yes, Update Bucket Policy」にしておき、対象のS3のバケットポリシーを自動生成されるようにします。

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

「Create Distribution」をクリックします。赤枠で囲んでいる部分がオリジンアクセスアイデンティティです。

オリジンアクセスアイデンティティ確認

S3のバケットポリシーを確認します。

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

オリジンアクセスアイデンティティが追加されていることが確認できます。

これでS3経由でアクセスできずにCloudFront経由でしかアクセスできないようになっているはずです。

S3のURL

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

CloudFrontのURL

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

アクセスの確認ができました。今度は署名付きCookieもしくは署名付きURLのみアクセス可能に制限します。

CloudFrontのBehaviorsの設定を確認します。Restrict Viewer Accessが「No」になっている場合は「Yes」に変更します。これで署名付きURL or Cookieで有効期限内のものしかアクセスが出来なくなります。

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

「Self」にチェックを入れると今のAWSアカウントIDのみとなります。

この設定で、再度、CloudFront経由でアクセスしてみます。

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

正常にアクセスできず、エラー内容が「キーペアIDのcookieが設定されていない」、的な内容に変わっていることが確認できます。

Restrict Bucket AccessとRestrict Viewer Access

Restrict Bucket Accessはオリジンアクセスアイデンティティで設定された権限でアクセス制御を行います。

Restrict Viewer Accessは署名によるアクセス制御を行います。

以下サイトが検証されているので参考ください。

参考サイト:https://doruby.jp/users/nakamatsu/entries/CloudFront%E3%81%AE%E3%80%8CRestrict-Bucket-Access%E3%80%8D%E3%81%A8%E3%80%8CRestrict-Viewer-Access%E3%80%8D%E3%81%AE%E9%81%95%E3%81%84%E3%81%8C%E3%82%88%E3%81%8F%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%A7%E6%A4%9C%E8%A8%BC%E3%81%97%E3%81%A6%E3%81%BF%E3%81%9F

CloudFront-Policy,CloudFront-Signature,CloudFront-Key-Pair-Idクッキーが必要

この3つのクッキーはシステムログイン時にでもヘッダ情報に設定してもらってからCloudFront + S3経由で署名付きCookieでアクセスすることになると思います。

ちなみにAPI Gateway + Lambdaでクッキーを設定する方法は「API Gateway+LambdaでヘッダにSet-Cookieを複数設定する方法」で書きましたが、スマートな方法ではないことは確かです。。

各クッキーに設定する値は以下の通りです。

クッキー
CloudFront-Policy base64エンコードされたJSON形式のポリシーステートメント
CloudFront-Signature 署名されたbase64エンコードバージョンのJSONポリシーステートメント
CloudFront-Key-Pair-Id アクティブなCloudFrontキーペアID

カスタムポリシーを作成する場合はこの3つが必要になります。署名するためにCloudFrontのキーペア作成も必要です。「AWS アカウントIDでCloudFrontのキーペアを取得する方法」参照ください。

ポリシーステートメントでプライベートコンテンツにワイルドカードが使える

以下、ポリシーステートメントの例です。URLにワイルドカードが使えるので便利です。

*を付けることによって再帰的にポリシーステートメントが効くようになります。(確認済み)

エポックタイムを指定して期間を指定することも可能です。

但し、いくつかカスタムポリシーには注意点があります。

1.Statementは配列になっているが1つだけしか指定できない

2.Resourceを配列にして複数URLを指定することができない

カスタムポリシーの構文が間違っている状態だと、ブラウザでアクセス時にMalformedPolicyエラーが発生します。

カスタムポリシーを使用した署名付き Cookie の設定 

policyファイル

{
  "Statement": [
    {
      "Resource":"http://d5e3fn38fzw4xx.cloudfront.net/*",
      "Condition":{
        "DateLessThan":{"AWS:EpochTime":1590000000}
      }
    }
  ]
}

ポリシーステートメントをbase64エンコードしていくのですが、参考サイトにある通り、cat policy | tr -d "\t\n\r "でポリシーステートメントの空白文字や改行コードを削除する必要があります。

※Javaで動作確認した際にaws-java-sdk-cloudfrontの1.11.0のSignerUtilsクラスのbuildCustomPolicyメソッドで生成されるカスタムポリシーには空白があるが動作し、問題ないみたいです

base64エンコードされたJSON形式のポリシーステートメント

LinuxやWSLの環境などで以下を実行します。出力結果をCloudFront-Policyに設定します。

cat policy | tr -d "\t\n\r " | openssl base64 | tr -- '+=/' '-_~'

署名されたbase64エンコードバージョンのJSONポリシーステートメント

LinuxやWSLの環境などで以下を実行します。出力結果をCloudFront-Signatureに設定します。

cat policy | tr -d "\t\n\r " | openssl sha1 -sign pk-XXX.pem | openssl base64 | tr -- '+=/' '-_~'

アクティブなCloudFrontキーペアID

キーペアのファイル名(pk-キーペアID.pem)をCloudFront-Key-Pair-Idに設定します。

確認方法

これでcurlコマンドでアクセスできることを確認します。

curl http://xxx.cloudfront.net/index.html -H \
'CoudFront-Policy=yyy; CloudFront-Signature=zzz; CloudFront-Key-Pair-Id=xyz'

Chromeで確認したかったので、開発者ツールのコンソールタブでdocument.cookieにcookieを設定しても確認することは可能です。

AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法

参考サイト

CloudFront+S3で署名付きURLでプライベートコンテンツを配信する

カスタムポリシーを使用する署名付き Cookie の設定 - Amazon CloudFront
ファイルへのエンドユーザーアクセスを制御するカスタムポリシーを使用して署名付き Cookie を設定します。

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/AmazonCloudFront_DevGuide.pdf

Javaで署名付きCookieを発行する

Javaで署名付きCookieを発行してみます。Javaと.netはpemファイルではなくderファイルで署名する必要があるようです。

ERROR: The request could not be satisfied

今回、derファイルはsecrets managerに登録しています。

secrets managerからderファイルをバイト配列で取得して、署名します。

build.gradleの依存関係は以下追加します。

implementation 'com.amazonaws:aws-java-sdk-cloudfront:1.11.0'
implementation 'com.amazonaws:aws-java-sdk-secretsmanager:1.11.415'

CloudFront-Policy、CloudFront-Signature、CloudFront-Key-Pair-Idを出力しているだけですが、Javaソースです。

package jp.co.confrage;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.stream.Stream;

import com.amazonaws.regions.Regions;
import com.amazonaws.services.cloudfront.CloudFrontCookieSigner;
import com.amazonaws.services.cloudfront.CloudFrontCookieSigner.CookiesForCustomPolicy;
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
import com.amazonaws.util.DateUtils;

public class Library {
  public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException {
    AWSSecretsManager client =
      AWSSecretsManagerClientBuilder.standard().withRegion(Regions.AP_NORTHEAST_1).build();

    GetSecretValueRequest getSecretValueRequest =
      new GetSecretValueRequest().withSecretId("hogekey"); // シークレットキー名
    GetSecretValueResult getSecretValueResult = client.getSecretValue(getSecretValueRequest);

    byte[] byteArray = new byte[getSecretValueResult.getSecretBinary().remaining()];
    getSecretValueResult.getSecretBinary().get(byteArray);

    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(byteArray);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    RSAPrivateKey privateKey = (RSAPrivateKey) kf.generatePrivate(keySpec);

    CookiesForCustomPolicy cookies =
      CloudFrontCookieSigner.getCookiesForCustomPolicy(
        "https://www.confrage.co.jp/*",
        privateKey,
        "APKWER9QJPY6MZHVCERQ",
        DateUtils.parseISO8601Date("2020-12-31T23:59:59.999Z"),
        null,
        "0.0.0.0/0");
    String cookiePolicy = cookies.getPolicy().getKey() + "=" + cookies.getPolicy().getValue();
    String cookieSignature =
      cookies.getSignature().getKey() + "=" + cookies.getSignature().getValue();
    String cookieKeyPairId =
      cookies.getKeyPairId().getKey() + "=" + cookies.getKeyPairId().getValue();
    String[] strarray = {cookiePolicy, cookieSignature, cookieKeyPairId};
    Stream.of(strarray).forEach(System.out::println);
  }
}

標準出力されるbase64されたポリシーなどを確認しようとしたのですが、提供されているJavaクラスのbuildCustomPolicyメソッドに半角スペースが入っているため、コマンドベースの出力結果と異なる為、比較ができません。

署名付きCookieでパス書き換え

CloudFrontのOrigin Pathを使えばパスを書き換えることが出来ます。さらに書き換えたURLに対して署名付きCookieを発行することが可能です。

署名付きCookieでパス書き換え

Origin Pathを使うことにより以下のようなことが実現可能です。

Origin Path
/dir/aaa/bbb

ポリシーのリソースに以下を指定します。

Resource
https://xxx.cloudfront.net/*

ワイルドカードは再帰的に効きます。以下2つのURLはともに署名付きCookieの対象です。

有効なURL
https://バケット名.s3-ap-northeast-1.amazonaws.com/dir/aaa/bbb/1.html
https://バケット名.s3-ap-northeast-1.amazonaws.com/dir/aaa/bbb/ccc/2.html

ポリシーのResourceにhttps://xxx.cloudfront.net/*を指定すれば、上記URL2つとも署名付きCookieが有効になります。

コメント

タイトルとURLをコピーしました