こちらは『ゆるWeb勉強会@札幌 Advent Calendar 2022』の18日目の記事です。
はじめに
S3に保存しているコンテンツを保護したい場合、署名付きURLや署名付きCookieを利用すると思います。
もう少しライトにアクセスを制限出来ないかと思い、CloudFront Functionsで
ハッシュの検証を試してみました。
実現したいこと
クエリパラメータsに指定したハッシュ値が正しい場合のみ、S3に保存してある画像を表示するようにします。
ハッシュ化はCloudFront Functionsで用意されているビルトインモジュール(crypto)を利用します。
アルゴリズムはsha256を使用し、ハッシュベースのメッセージ認証コード (HMAC)を生成します。
今回リクエスト側の処理は省略します。
テスト用にPHPでハッシュ値を生成しました。
前提条件
- 画像はS3にアップロード済
- S3はCloudFront経由でのみアクセスする設定済
- CloudFrontからS3への接続設定済
コード
CloudFront Functions用
JWT認証のコードを参考にしています。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/example-function-validate-token.html
ハッシュ化部分
var crypto = require('crypto');
crypto.createHmac('sha256', {シークレット値}).update({ハッシュ化したい文字列}).digest('hex')
crypto.createHmac(algorithm, secret key)
指定された algorithm と secret key を使用する HMAC オブジェクトを作成して返します。アルゴリズムは md5、sha1、sha256 のいずれかを使用します。hmac.update(data)
指定された data を使用して HMAC コンテンツを更新します 。hmac.digest([encoding])
hmac.update() を使用して渡されたすべてのデータのダイジェストを計算します。エンコードは hex、base64、base64url のいずれかを使用します。
var crypto = require('crypto');
//バリデーションエラー時のレスポンス
var response401 = {
statusCode: 401,
statusDescription: 'Unauthorized'
};
//ハッシュ値のバリデーション
function verifyRequest(fwdUri, token, key, signingMethod, signingType) {
// check token
if (!token) {
throw new Error('No token supplied');
}
if (!_verify(fwdUri, token, key, signingMethod, signingType)) {
throw new Error('Signature verification failed');
}
return fwdUri;
}
function _verify(input, token, key, method, type) {
if (type === "hmac") {
return (token === _sign(input, key, method));
}
else {
throw new Error('Algorithm type not recognized');
}
}
function _sign(input, key, method) {
return crypto.createHmac(method, key).update(input).digest('hex');
}
function handler(event) {
var request = event.request;
//Secret key used to verify token.
//Update with your own key.
var key = "penguin";
var signingMethod = 'sha256';
var signingType = 'hmac';
// If no token, then generate HTTP redirect 401 response.
if (!request.querystring.s) {
console.log("Error: No token in the querystring");
return response401;
}
var fwdUri = event.request.uri;
var token = request.querystring.s.value;
try {
verifyRequest(fwdUri, token, key, signingMethod, signingType);
}
catch (e) {
console.log(e);
return response401;
}
//Remove the token from the query string if valid and return.
delete request.querystring.s;
console.log("Valid token");
return request;
}
リクエストのテスト用ハッシュ作成
以下の内容で作成します。
参照S3:/images/sample.jpg
secret:penguin
<?php
$s3Path = "/images/sample.jpg";
$hased_string = hash_hmac('sha256', $s3Path, 'penguin');
print_r($hased_string.PHP_EOL);
$ php makeHash.php
fcf3f9b449720bfc1fd32f8fe3a2d0cb72ebe14e6ea86cb7d6fdc60e4b7dd521
CloudFront Functionsの作成
チュートリアルの通りに設定します。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/functions-tutorial.html
-
マネジメントコンソールからCloudFrontを開きます。
https://console.aws.amazon.com/cloudfront/v3/home -
構築タブをクリックし、開発のテキストにコードを貼り付けます。
-
CloudFrontディストリビューションと紐づけます。
イベントタイプは「Viewer Request」を選択します。
実行結果
正しくないハッシュ値の場合
s=hogeなどを渡すと、指定した401エラーが返却されます。
正しいハッシュ値を渡した場合
まとめ
初めてCloudFront Functionsを触りましたが、Lambda@Edgeよりは簡単に実装できました。
例えば指定したサイズを含むハッシュ値を作成し、画像のリサイズ処理の前に検証すると良さそうです。
サイズを微妙に変更したリクエストが大量に届いても、Lambda@Edgeの実行前に制限できますね。
制限もありますので、クオータや紹介ページで確認しておきましょう!
P.S.ヘッダに認証キーを設定する方法が書かれている記事を見つけました。
クエリでハッシュ値を渡すよりおしゃれです。
参考資料