# 1. Presigned URL とは
# 背景的ななにか
AWS では、サービスの API エンドポイントへアクセスするとき、そのサービスへのアクセス権限があることを証明するためにリクエストに署名を含める。 署名はを作成するためには例えば以下の情報が入力として必要になる。
- 認証情報(アクセスキー、シークレットキー)
- HTTP メソッド
- リクエストパス
- 一部の HTTP リクエストヘッダ
- 一部のクエリ文字
- リクエスト時刻
詳細はこちら
基本的にリクエストを発行するクライアント側で認証情報を管理する必要が出てくることを考えるが、認証情報が第三者へ漏れるとサービスに悪意のあるアクセスが行われるリスクがある。特に Web アプリなどクライアント側がパブリックな環境である場合は気軽に認証情報をクライアント側に持ち出せない。
そのリスクを抑えるために、あらかじめ署名をサーバサイドで作成し、署名をクライアントに渡して使ってもらうというのが Presigned リクエスト。
通常の署名にはリクエストの時刻として date(x-amz-date)
の情報が含まれ、署名の検証時に実際のリクエストの日時とズレが有る際にエラーが発生するため、いつ行うか正確に予測できないようなリクエストの場合、署名を事前に作成できない。
S3 では、その問題の対応として、Presigned URL という機能が用意されている。Presigned URL では、署名をクエリ文字として含む URL を事前に生成する機能で、生成された URL を利用することで S3 オブジェクトへアクセスできる。Presigned URL による署名には date
ではなく、署名の有効期限(Expires
)を含める。
Presigned URL の署名は有効期限内であれば有効なため、事前に署名を作成し時間に余裕を持ってリクエストできる。
また、署名の作成時に S3 バケットやオブジェクトなどの情報が署名に含まれることになるので生成された URL が漏洩しても影響範囲を抑えられる。
# Presigned URL の作成
上で挙げたような情報を入力としてハッシュ値を計算する。(詳細は以下のリンク)
- Authenticating Requests: Using Query Parameters (AWS Signature Version 4)
- Authenticating REST Requests
各言語の AWS SDK の関数として Presigned URL を生成する機能が用意されていることが多い。
シンプルな GET リクエストなら AWS CLI の presign コマンド が便利。以下のコマンドでは 1 週間の有効期限を持った署名付き pre-signed URL を作成できる。
$ aws s3 presign s3://<バケット名>/<オブジェクトのパス> --expires-in 604800
JavaScript の SDK であれば以下のようなコードで Presigned URL を作成できる
var AWS = require("aws-sdk");
// SigV4が正しく利用されるために署名バージョンとリージョンを指定
const s3 = new AWS.S3({
apiVersion: "2006-03-01",
signatureVersion: "v4",
region: "<リージョン>",
});
var params = { Bucket: "<バケット名>", Key: "<オブジェクトのパス>" };
// GetObject の pre-signed URL を作成
var url = s3.getSignedUrl("getObject", params);
console.log("The URL is", url);
生成した Presigned URL をクライアントサイドに渡すことで、認証情報を直接クライアントに公開せずに署名を利用したリクエストを実現できる。
なお、POST の場合は Presigend "URL" というわけではなく、フォームデータに含めることを想定した署名の情報を作成する関数がある。
const params = {
Bucket: "<バケット名>",
Fields: {
key: "<オブジェクトのパス>",
},
};
// POST の pre-signed リクエストのための情報を作成
s3.createPresignedPost(params, function(err, data) {
if (err) {
console.error("Presigning post data encountered an error", err);
} else {
console.log("The post data is", data);
}
});