3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

CloudflareAdvent Calendar 2022

Day 22

Cloudflare Workers から HMAC 認証を使って Google Cloud Storage を fetch してキャッシュする

Last updated at Posted at 2023-06-04

はじめに

バックエンドサーバーとして公開状態でないオブジェクトストレージを利用するケースで、

オリジンサーバーとして Google Cloud Storage のプライベートオブジェクトにアクセスできることを確認します。

Google Cloud Storage で HMAC アクセスキーを発行

HMAC アクセスキーの発行は、こちらを参考にしました。

Workers コード

上記のコードを使います。ポイントは以下の点です

cache-api-workers.js
import { AwsClient } from 'aws4fetch'

const hostname = `${GCS_BUCKET}.storage.googleapis.com`

const aws = new AwsClient({
	accessKeyId: GCS_ACCESS_KEY_ID,
	secretAccessKey: GCS_SECRET_ACCESS_KEY,
	service: "s3",
});

addEventListener('fetch', function (event) {
	event.respondWith(handleRequest(event))
});

async function handleRequest(event) {
	const request = event.request
	const cacheUrl = new URL(request.url);
	const cacheKey = new Request(cacheUrl.toString(), request);
	const cache = caches.default;
	let response = await cache.match(cacheKey);

	if (!response) {
		console.log(
			`Response for request url: ${request.url} not present in cache. Fetching and caching request.`
		);
		const pathname = cacheUrl.pathname;
		//console.log(pathname);
		const gcsurl = `https://${hostname}${pathname}`
		const signedRequest = await aws.sign(gcsurl);
		response = await fetch(signedRequest, {
			/* This does not work because authorization header result cf-cache-status: BYPASS
			"cf": {
				// resolveOverride,
				cacheEverything: true,
				cacheTtl: 3,
			}*/
		})
		response = new Response(response.body, response);
		response.headers.set("Cache-Control", "s-maxage=30");
		//response.headers.append("Cloudflare-CDN-Cache-Control", "max-age=30")
		console.log(JSON.stringify([...request.headers]));
		console.log(JSON.stringify([...response.headers]));
		event.waitUntil(cache.put(cacheKey, response.clone()));
		console.log(`Response from Origin`);
	} else {
		console.log(`Cache hit for: ${request.url}.`);
	}
	return response;
}

Workers デプロイ

GitHub からクローンしたコードと、シークレット変数設定をおこないます。

git clone https://github.com/kyouheicf/private-access-to-gcs.git && cd $(basename $_ .git)

npm install aws4fetch
wrangler secret put GCS_BUCKET
wrangler secret put GCS_ACCESS_KEY_ID
wrangler secret put GCS_SECRET_ACCESS_KEY

また、以下のようにルート設定を追加します。

vi wrangler.toml
wrangler.toml
routes = [
	{ pattern = "private-access-to-gcs.example.com/*", zone_name = "example.com" }
]

以下のコマンドでデプロイします。

wrangler publish src/cache-api-worker.js

Cache API での確認

以下のコマンドで確認できます。

初回は cf-cache-status: MISS ですが、

% curl -sv https://private-access-to-gcs.example.com/private/Tokyo.png |& egrep "cf-cache-status|expires:|age:|cache-control"        
< cf-cache-status: MISS
< cache-control: s-maxage=30
< expires: Sun, 04 Jun 2023 19:11:34 GMT

次回以降は cf-cache-status: HIT となります。

% curl -sv https://private-access-to-gcs.example.com/private/Tokyo.png |& egrep "cf-cache-status|expires:|age:|cache-control" 
< cf-cache-status: HIT
< age: 5
< cache-control: public, max-age=14400
< expires: Sun, 04 Jun 2023 19:11:51 GMT

Image Resizing リクエスト

Image Resizing Worker では "origin-auth": "share-publicly" のオプションが用意されているため、こちらを使います。

Google Cloud Storage の場合、Authorizationx-amz-content-sha256x-amz-date を使うため、問題ありません。

aws4fetch を使います。R2 向けのサンプルコードが参考になります。

以下のコマンドでデプロイします。

wrangler publish src/image-resizing-private-gcs.js
image-resizing-private-gcs.js 
import { AwsClient } from 'aws4fetch'

const hostname = `${GCS_BUCKET}.storage.googleapis.com`

const aws = new AwsClient({
	accessKeyId: GCS_ACCESS_KEY_ID,
	secretAccessKey: GCS_SECRET_ACCESS_KEY,
	service: "s3",
});

addEventListener('fetch', function (event) {
	event.respondWith(handleRequest(event.request))
});

async function handleRequest(request) {
	const url = new URL(request.url);
	const pathname = url.pathname;
	const gcsurl = `https://${hostname}${pathname}`
	const signedRequest = await aws.sign(gcsurl);
	console.log(JSON.stringify([...signedRequest.headers]));
	return await fetch(signedRequest, {
		"cf": {
			// resolveOverride,
			cacheEverything: true,
			cacheTtl: 30,
			image: {
				//anim: true,
				//background: "#RRGGBB",
				//blur: 50,
				//border: {color: "#FFFFFF", width: 10},
				//brightness: 0.5,
				//compression: "fast",
				//contrast: 0.5,
				//dpr: 1,
				//fit: "scale-down",
				//format: "webp",
				//gamma: 0.5,
				//gravity: "auto",
				//height: 250,
				//metadata: "keep",
				//onerror: "redirect",
				//quality: 50,
				rotate: 90,
				"origin-auth": "share-publicly"
				//sharpen: 2,
				//trim: {"top": 12,  "right": 78, "bottom": 34, "left": 56,},
				//width: 250,
			}
		}
	})
}

Image Resizing でのキャッシュ確認

以下のコマンドで確認できます。

初回は cf-cache-status: MISS ですが、

% curl -sv https://private-access-to-gcs.example.com/private/Tokyo.png |& egrep "cf-cache-status|expires:|age:|cache-control|cf-resized:"
< cf-cache-status: MISS
< cache-control: private, max-age=14400
< cf-resized: internal=ok/b q=0 n=878+0 c=20+111 v=2023.5.0 l=90529
< warning: cf-images 299 "cache-control is too restrictive"

次回以降は cf-cache-status: HIT となります。

% curl -sv https://private-access-to-gcs.example.com/private/Tokyo.png |& egrep "cf-cache-status|expires:|age:|cache-control|cf-resized:"
< cf-cache-status: HIT
< cache-control: private, max-age=14400
< cf-resized: internal=ok/b q=0 n=878+0 c=20+111 v=2023.5.0 l=90529
< warning: cf-images 299 "cache-control is too restrictive"

この warning ヘッダの意味ですが、オリジンが cache-control: private を返しているため、オリジナルソース画像がキャッシュされない旨の内容です(リサイズ済み画像はキャッシュされる)。

cf-resized: internal=ok/bb は、オリジナルソース画像のキャッシュステータスを表し、bBYPASS の意味です。

cf-resized は一度 cf-cache-status: HIT したら更新されず、リサイズパイプラインがトリガーされたときにのみ、cf-resizedヘッダが設定されます)

Source image is cached using regular caching rules.

BYPASS: The origin server instructed Cloudflare to bypass cache via a Cache-Control header set to no-cache,private, or max-age=0 even though Cloudflare originally preferred to cache the asset. BYPASS is returned when enabling Origin Cache-Control. Cloudflare also sets BYPASS when your origin web server sends cookies in the response header. If the Request to your origin includes an Authorization header, its response will be also BYPASS.

また、オリジンの Google Cloud Storage では、パブリック公開オブジェクト以外で Cache-Control をカスタム適用することができないため、今回のような認証付きプライベートオリジンに対しては、オリジナルソース画像へのリクエストが新規リサイズの必要に応じて都度発生する形になります。

Cache-Control only applies when accessing objects that:
Are publicly accessible.

そのため、Image Resizing を使う場合には、Cache-Control を制御可能な形のオリジンを利用できることが推奨です。

まとめ

バックエンドサーバーとして公開状態でないオブジェクトストレージを利用するケースで、オリジンサーバーとして Google Cloud Storage のプライベートオブジェクトにアクセスできることが無事確認できましたが、Image Resizing の際は留意する点があることが確認でき、よかったです。

参考リンク

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?