コンテンツにスキップ

プレサインドURL

プレサインドURLは、トークンシークレットを明らかにすることなく、バケットへの直接アクセスを共有するためのS3の概念です。プレサインドURLは、そのURLを持つ誰でもR2バケットのS3互換エンドポイントに対してアクションを実行することを許可します。デフォルトでは、S3エンドポイントはトークンによって署名されたAUTHORIZATIONヘッダーを必要とします。すべてのプレサインドURLには、AUTHORIZATIONヘッダーに存在する署名情報を含むS3パラメータと検索パラメータがあります。実行可能なアクションは特定のリソース、操作に制限され、関連するタイムアウトがあります。

R2には3種類のリソースがあります:

  1. アカウント: アカウントレベルの操作(CreateBucketListBucketsDeleteBucketなど)の場合、識別子はアカウントIDです。
  2. バケット: バケットレベルの操作(ListObjectsPutBucketCorsなど)の場合、識別子はアカウントIDとバケット名です。
  3. オブジェクト: オブジェクトレベルの操作(GetObjectPutObjectCreateMultipartUploadなど)の場合、識別子はアカウントID、バケット名、およびオブジェクトパスです。

識別子のすべての部分はプレサインドURLの一部です。

リクエストが署名された後にアクセスされるリソースを変更することはできません。たとえば、異なるバケット内の同じオブジェクトにアクセスするためにバケット名を変更しようとすると、403SignatureDoesNotMatchのエラーコードが返されます。

プレサインドURLには定義された有効期限が必要です。1秒から7日(604,800秒)までの将来のタイムアウトを設定できます。URLには、URLが生成された時刻(X-Amz-Date)とタイムアウト(X-Amz-Expires)が検索パラメータとして含まれます。これらの検索パラメータは署名されており、改ざんすると403SignatureDoesNotMatchのエラーコードが返されます。

プレサインドURLは、R2との通信なしに生成され、R2バケットの資格情報にアクセスできるアプリケーションによって生成される必要があります。

プレサインドURLのユースケース

アプリケーションにR2へのアクセスを付与する方法は3つあります:

  1. アプリケーションが独自のR2 APIトークンのコピーを持っている。
  2. アプリケーションがボールトアプリケーションからR2 APIトークンのコピーを要求し、そのトークンをローカルに永続的に保存しないことを約束する。
  3. アプリケーションが中央アプリケーションにプレサインドURLを要求し、そのURLを使用してアクションを実行する。

シナリオ1と2では、アプリケーションまたはボールトアプリケーションが侵害された場合、トークンの保持者は任意のアクションを実行できます。

シナリオ3では、資格情報が秘密に保たれます。中央アプリケーションにプレサインドURLリクエストを行うアプリケーションがそのURLを漏洩した場合でも、中央アプリケーションのキー保存システムが侵害されていなければ、影響は署名された特定のリソースに対する1つの操作に制限されます。

さらに、中央アプリケーションは監視、監査、ログ記録タスクを実行できるため、特定のリソースに対して操作を実行するためにリクエストが行われた時期を確認できます。セキュリティインシデントが発生した場合、中央アプリケーションのログ機能を使用してインシデントの詳細を確認できます。

中央アプリケーションはポリシーの強制も行うことができます。たとえば、リソースのアップロードを担当するアプリケーションがある場合、特定のバケットまたはバケット内のフォルダーへのアップロードを制限できます。リクエストするアプリケーションは、認証サービスからJSON Web Token(JWT)を取得して、中央アプリケーションへのリクエストを署名できます。中央アプリケーションは、JWTに含まれる情報を使用して、受信リクエストパラメータを検証します。

中央アプリケーションは、たとえばCloudflare Workerである可能性があります。Workerシークレットは、Workersランタイムで実行されているスクリプトの外部から取得することは暗号的に不可能です。シークレットのコピーを他の場所に保存せず、コードがシークレットをどこかにログに記録しない限り、Workerシークレットは安全に保たれます。ただし、前述のように、プレサインドURLはR2の外部で生成され、必要なのはシークレットと署名アルゴリズムの実装だけであるため、どこでも生成できます。

プレサインドURLの別の潜在的なユースケースはデバッグです。たとえば、アプリケーションをデバッグしていて、プロダクション環境の特定のテストオブジェクトへの一時的なアクセスを付与したい場合、基盤となるトークンを共有することなく、これを行うことができます。

サポートされているHTTPメソッド

R2は現在、プレサインドURLを生成する際に以下のメソッドをサポートしています:

  • GET: ユーザーがバケットからオブジェクトを取得できるようにします
  • HEAD: ユーザーがバケットからオブジェクトのメタデータを取得できるようにします
  • PUT: ユーザーがバケットにオブジェクトをアップロードできるようにします
  • DELETE: ユーザーがバケットからオブジェクトを削除できるようにします

POSTは、ネイティブHTMLフォームを介してアップロードを実行しますが、現在はサポートされていません。

プレサインドURLの生成

以下の例を参照してプレサインドURLを生成します:

WorkersによるプレサインドURLの代替

プレサインドURLの有効な代替設計は、セキュリティポリシーを実装するbindingを持つWorkerを使用することです。

可能なユースケースは、アプリケーションが特定のURLにのみアップロードできるように制限することです。プレサインドURLを使用すると、中央署名アプリケーションは、Cloudflare Workers、workerd、または他のプラットフォームで実行される次のJavaScriptコードのようになります。

Workerがhttps://example.com/uploads/dog.pngへのリクエストを受け取った場合、ユーザーが/uploads/dog.pngパスのR2バケットにアップロードできるプレサインドURLを返します。

import { AwsClient } from "aws4fetch";
const r2 = new AwsClient({
accessKeyId: "",
secretAccessKey: "",
});
export default {
async fetch(req): Promise<Response> {
// これは、aws4fetchを使用してプレサインドURLを生成する例です。
// このWorkerはそのまま使用すべきではありません。リクエストを認証していないため、
// 誰でもバケットにアップロードできることになります。
//
// 認証を実装することを検討してください。たとえば、リクエストヘッダーに事前共有されたシークレットを使用します。
const requestPath = new URL(req.url).pathname;
// バケットのルートにはアップロードできません
if (requestPath === "/") {
return new Response("ファイルパスが不足しています", { status: 400 });
}
const bucketName = "";
const accountId = "";
const url = new URL(
`https://${bucketName}.${accountId}.r2.cloudflarestorage.com`
);
// 元のパスを保持します
url.pathname = requestPath;
// プレサインドURLのカスタム有効期限を秒単位で指定します
url.searchParams.set("X-Amz-Expires", "3600");
const signed = await r2.sign(
new Request(url, {
method: "PUT",
}),
{
aws: { signQuery: true },
}
);
// 呼び出し元はこのURLを使用してそのオブジェクトにアップロードできます。
return new Response(signed.url, { status: 200 });
},
// ... 他の種類のリクエストを処理します
} satisfies ExportedHandler;

Workerコードには、設定やトークンシークレットがまったく含まれていないことに注意してください。代わりに、アップロードするバケットを表すバケットにバインドするwrangler.toml bindingを作成します。さらに、認証はアップロードとインラインで処理されるため、レイテンシを削減できます。

場合によっては、Workersを使用すると特定の機能をより簡単に実装できます。たとえば、ユーザーがパスに一度だけアップロードできるようにするための書き込み保証を提供したい場合、プレサインドURLを使用すると、特定のヘッダーに署名し、送信者にそれらを送信させる必要があります。前のWorkerを変更して追加のヘッダーに署名できます:

const signed = await r2.sign(
new Request(url, {
method: "PUT",
}),
{
aws: { signQuery: true },
headers: {
"If-Unmodified-Since": "Tue, 28 Sep 2021 16:00:00 GMT",
},
}
);

呼び出し元は、URLを使用するために同じIf-Unmodified-Sinceヘッダーを追加する必要があります。呼び出し元はヘッダーを省略したり、異なるヘッダーを使用したりすることはできません。呼び出し元が異なるヘッダーを使用した場合、プレサインドURLの署名が一致せず、403/SignatureDoesNotMatchが返されます。

Workerでは、アップロードを次のように変更します:

const existingObject = await env.DROP_BOX_BUCKET.put(
url.toString().substring(1),
request.body,
{
onlyIf: {
// 2021年9月28日以前にアップロードされたオブジェクトはありません。
uploadedBefore: new Date(1632844800000),
},
}
);
if (existingObject?.etag !== request.headers.get('etag')) {
return new Response('オブジェクトの上書きの試み', { status: 400 });
}

Cloudflare Workersには、考慮すべきいくつかの制限があります:

  • Workerに100 MiB(ビジネス顧客の場合は200 MiB)をアップロードすることはできません。
  • エンタープライズ顧客はデフォルトで500 MiBをアップロードでき、アカウントチームにこの制限を引き上げるように依頼できます。
  • 前提条件の失敗の検出は、R2バインディングと比較してプレサインドURLの方が現在は簡単です。

これらの制限は、R2の条件付きアップロードの拡張に依存します。AmazonのS3サービスは、現時点ではそのような機能を提供していません。

プレサインドURLとパブリックバケットの違い

プレサインドURLは、パブリックバケットといくつかの表面的な類似点を共有しています。特定のオブジェクトに対するGET/HEAD操作のためにプレサインドURLを発行する場合、プレサインドURLの機能は主にパブリックバケットと似ています。顕著な例外は、オブジェクトに関連付けられたカスタムメタデータがx-amz-meta-プレフィックス付きのヘッダーに表示されることです。エラー応答は、通常の非プレサインドS3アクセスと同様にXMLドキュメントとして返されます。

プレサインドURLは、任意のS3操作のために生成できます。プレサインドURLが生成された後は、署名された有効期限日まで、URLの保持者が望むだけ再利用できます。

パブリックバケットは、通常のHTTPエンドポイントで利用可能です。デフォルトでは、パブリックバケットには認証やアクセス制御はありません。パブリックバケットのURLを持つ誰でも、そのパブリックバケット内のオブジェクトにアクセスできます。R2バケットを公開するためにカスタムドメインを使用している場合、Cloudflareゾーンと同様に認証とアクセス制御を管理できます。パブリックバケットは、既知のオブジェクトパスに対してのみGET/HEADを提供します。パブリックバケットのエラーはHTMLページとして表示されます。

プレサインドURLとパブリックバケットの選択は、特定のユースケースに依存します。アーキテクチャが特定の状況でパブリックバケットを使用し、別の状況でプレサインドURLを使用する必要がある場合、両方を使用することもできます。プレサインドURLは、URLのコピーを取得した人にアカウントIDとバケット名を公開します。パブリックバケットのURLにはアカウントIDやバケット名は含まれません。通常、プレサインドURLはエンドユーザーやブラウザと直接共有することはなく、内部アプリケーションで使用されます。

制限

プレサインドURLは、<accountid>.r2.cloudflarestorage.com S3 APIドメインでのみ使用でき、カスタムドメインでは使用できません。代わりに、WAFの一般的なHMAC検証機能を使用できます。これはProプラン以上が必要です。

関連リソース