web-dev-qa-db-ja.com

AWSSDKを介して署名されたS3およびCloudfrontURLを作成する

AWS SDKを使用して、CloudFrontでも機能するS3バケット内のオブジェクトへの署名付きURLを正常に生成した人はいますか? JavaScript AWS SDK を使用していますが、S3リンクを介して署名付きURLを生成するのは非常に簡単です。プライベートバケットを作成し、次のコードを使用してURLを生成しました。

var AWS = require('aws-sdk')
  , s3 = new AWS.S3()
  , params = {Bucket: 'my-bucket', Key: 'path/to/key', Expiration: 20}

s3.getSignedUrl('getObject', params, function (err, url) {
  console.log('Signed URL: ' + url)
})

これはうまく機能しますが、CloudFront URLをユーザーに公開して、CDNを使用することでダウンロード速度を向上させることもできます。アクセスを許可するようにバケットポリシーを変更するCloudFrontディストリビューションをセットアップしました。ただし、これを行った後、CloudFront URLを介して任意のファイルにアクセスでき、Amazonは私のリンクの署名を無視しているように見えました。これについてもう少し読んだ後、人々がCloudFrontで動作する署名付きURLを取得するために.pemファイルを生成するのを見ましたが、なぜこれがS3に必要ないのですか? getSignedUrlメソッドは、AWSシークレットキーとAWSアクセスキーを使用して署名を行うだけのようです。誰かが以前にこのような設定を行ったことがありますか?

更新:さらに調査したところ、CloudFrontはS3 [link] とは完全に異なるURL署名を処理しているようです。ただし、Javascriptを使用して署名付きCloudFrontURLを作成する方法についてはまだわかりません。

20
Jason Sims

更新:署名機能を以下のサンプルコードからNPMの aws-cloudfront-sign パッケージに移動しました。そうすれば、このパッケージを要求してgetSignedUrl()を呼び出すことができます。


さらに調査した結果、 この回答Botoライブラリ で見つけたメソッドの組み合わせのような解決策を見つけました。 S3URL署名がCloudFrontURL署名とは異なる方法で処理されるのは事実です。 S3リンクに署名する必要がある場合は、最初の質問のサンプルコードで問題なく動作します。ただし、CloudFrontディストリビューションを利用する署名付きURLを生成する場合は、少し複雑になります。これは、CloudFrontURL署名が現在AWSSDKでサポートされていないため、自分で署名を作成する必要があるためです。これも行う必要がある場合の基本的な手順は次のとおりです。すでにS3バケットがセットアップされていると仮定します。

CloudFrontを設定する

  1. CloudFrontディストリビューションを作成する
  2. 次の設定でOriginを構成します
    • オリジンドメイン名:{your-s3-bucket}
    • バケットアクセスの制限:はい
    • バケットの読み取り権限を付与する:はい、バケットポリシーを更新します
  3. CloudFrontキーペアを作成します。これを行うことができるはずです ここ

署名付きCloudFrontURLを作成する

署名されたCloudFrontURLを優れたものにするには、RSA-SHA1を使用してポリシーに署名し、それをクエリパラメーターとして含める必要があります。カスタムポリシーの詳細については、 ここ を参照してください。ただし、以下のサンプルコードには、起動して実行するための基本的なポリシーが含まれています。サンプルコードはNode.js用ですが、このプロセスはどの言語にも適用できます。

var crypto = require('crypto')
  , fs = require('fs')
  , util = require('util')
  , moment = require('moment')
  , urlParse = require('url')
  , cloudfrontAccessKey = '<your-cloudfront-public-key>'
  , expiration = moment().add('seconds', 30)  // Epoch-expiration-time

// Define your policy.
var policy = {
   'Statement': [{
      'Resource': 'http://<your-cloudfront-domain-name>/path/to/object',
      'Condition': {
         'DateLessThan': {'AWS:EpochTime': '<Epoch-expiration-time>'},
      }
   }]
}

// Now that you have your policy defined you can sign it like this:
var sign = crypto.createSign('RSA-SHA1')
  , pem = fs.readFileSync('<path-to-cloudfront-private-key>') 
  , key = pem.toString('ascii')

sign.update(JSON.stringify(policy))
var signature = sign.sign(key, 'base64')

// Finally, you build the URL with all of the required query params:
var url = {
  Host: '<your-cloudfront-domain-name>',
  protocol: 'http',
  pathname: '<path-to-s3-object>'
}    
var params = {
  'Key-Pair-Id=' + cloudfrontAccessKey,
  'Expires=' + expiration,
  'Signature=' + signature
}
var signedUrl = util.format('%s?%s', urlParse.format(url), params.join('&'))

return signedUrl
23
Jason Sims

私のコードがJasonSimsのコードで機能するためには、ポリシーをbase64に変換し、次のように最終的なsignedUrlに追加する必要もありました。

sign.update(JSON.stringify(policy))
var signature = sign.sign(key, 'base64')

var policy_64 = new Buffer(JSON.stringify(policy)).toString('base64'); // ADDED

// Finally, you build the URL with all of the required query params:
var url = {
  Host: '<your-cloudfront-domain-name>',
  protocol: 'http',
  pathname: '<path-to-s3-object>'
}    
var params = {
  'Key-Pair-Id=' + cloudfrontAccessKey,
  'Expires=' + expiration,
  'Signature=' + signature,
  'Policy=' + policy_64  // ADDED 
}
2

AWSには、CloudFrontの署名付きURLとCookieの作成を支援するための組み込みのクラスと構造がいくつか含まれています。 Jason Simsによる優れた回答と一緒にこれらを利用して、わずかに異なるパターンで機能させるようにしました(これは、彼が作成したNPMパッケージと非常に似ているようです)。

つまり、署名されたURLとCookieを作成するプロセスを抽象化するAWS.CloudFront.Signerタイプの説明です。

export class Signer {
    /**
     * A signer object can be used to generate signed URLs and cookies for granting access to content on restricted CloudFront distributions.
     * 
     * @param {string} keyPairId - The ID of the CloudFront key pair being used.
     * @param {string} privateKey - A private key in RSA format.
     */
    constructor(keyPairId: string, privateKey: string);

    ....
}

また、ポリシーJSON文字列を含むオプション、またはURLと有効期限を含むポリシーを含まないオプションのいずれか。

export interface SignerOptionsWithPolicy {
    /**
     * A CloudFront JSON policy. Required unless you pass in a url and an expiry time. 
     */
    policy: string;
}
export interface SignerOptionsWithoutPolicy {
    /**
     * The URL to which the signature will grant access. Required unless you pass in a full policy.
     */
    url: string
    /**
     * A Unix UTC timestamp indicating when the signature should expire. Required unless you pass in a full policy.
     */
    expires: number
}

実装例:

import aws, { CloudFront } from 'aws-sdk';

export async function getSignedUrl() {

    // https://abc.cloudfront.net/my-resource.jpg
    const url = <cloud front url/resource>;

    // Create signer object - requires a public key id and private key value
    const signer = new CloudFront.Signer(<public-key-id>, <private-key>);

    // Setup expiration time (one hour in the future, in this case)
    const expiration = new Date();
    expiration.setTime(expiration.getTime() + 1000 * 60 * 60);
    const expirationEpoch = expiration.valueOf();

    // Set options (Without policy in this example, but a JSON policy string can be substituted)
    const options = {
        url: url,
        expires: expirationEpoch
    };

    return new Promise((resolve, reject) => {
        // Call getSignedUrl passing in options, to be handled either by callback or synchronously without callback
        signer.getSignedUrl(options, (err, url) => {
            if (err) {
                console.error(err.stack);
                reject(err);
            }
            resolve(url);
        });
    });
}
0
msg45f