Google Cloud Storage origin (private)
Use AWS compat mode to make authenticated requests to your GCS bucket.
VCL
Use this solution in your VCL service (click RUN below to test this solution or clone it to make changes):
Compute
Use this solution in your Compute service:
- JavaScript
- Rust
package.json
JavaScript
{"dependencies":{"crypto-js":"^4.1.1","date-fns":"^2.28.0"}}
index.js
JavaScript
import * as crypto from 'crypto-js';import { formatISO } from 'date-fns';
// Hash of empty string, used for authentication string generation// caculated from crypto.SHA256('');const EMPTY_HASH = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';// The GCS access id that is linked to a specific service or user account.const ACCESS_ID = 'XXXXXXXX'// A 40-character Base-64 encoded string that is linked to the access ID above.const SECRET = 'YYYYYYYY'const GCS_REGION = 'asia-northeast1';const GCS_SERVICE = 'storage';const GCS_BUCKET_NAME = 'compute-at-edge-demo';const HOST = `${GCS_BUCKET_NAME}.storage.googleapis.com`;const SIGNED_HEADERS = 'host;x-goog-content-sha256;x-goog-date';
addEventListener('fetch', event => event.respondWith(handleRequest(event)));
async function handleRequest(event) { // Ignore the query string from client let req = removeQuery(event.request); // Only generate authorize header for GET and HEAD request // Pass PURGE to backend as it is // Block other kinds of method if (req.method === 'GET' || req.method === 'HEAD') { authorizeRequest(req);
let res = await fetch(req, { backend: 'gcs_backend', });
// Remove some headers returned from GCS res.headers.delete('x-guploader-uploadid'); res.headers.delete('x-goog-hash'); res.headers.delete('x-goog-storage-class'); res.headers.delete('server'); res.headers.delete('alt-svc');
return res; } else if (req.method === 'PURGE') { // When doing the purge, we need to make sure the cache key matches // Cache key is cacualted from URL and HOST header of request sent to backend // URL of backend request has no query string req.headers.set('host', HOST);
return await fetch(req, { backend: 'gcs_backend', }); } else { return new Response("This method is not allowed", { status: 405 }); }}
// Call the tasks and build the request with the authorization headerfunction authorizeRequest(req) { const timeStampISO8601Format = formatISO(new Date(), { format: 'basic' }); const YYYYMMDD = timeStampISO8601Format.slice(0, 8);
console.log(YYYYMMDD); const canonicalRequest = generateCanonicalRequest(req, timeStampISO8601Format); console.log(`canonicalRequest = ${canonicalRequest}`);
const stringToSign = generateStringToSign(timeStampISO8601Format, YYYYMMDD, canonicalRequest); console.log(`stringToSign = ${stringToSign}`);
const signature = generateSignature(YYYYMMDD, stringToSign); console.log(`signature = ${signature}`);
const authorizationValue = `GOOG4-HMAC-SHA256 Credential=${ACCESS_ID}/${YYYYMMDD}/${GCS_REGION}/${GCS_SERVICE}/goog4_request,SignedHeaders=${SIGNED_HEADERS},Signature=${signature}`; console.log(`authorizationValue = ${authorizationValue}`);
req.headers.set('host', HOST); req.headers.set('authorization', authorizationValue); req.headers.set('x-goog-content-sha256',EMPTY_HASH); req.headers.set('x-goog-date', timeStampISO8601Format);}
// Task 1: Create a Canonical Request// https://cloud.google.com/storage/docs/authentication/canonical-requestsfunction generateCanonicalRequest(req, timeStampISO8601Format) { const httpMethod = req.method;
// Do url decode and re-encode in case some client are not do url encoding. // https://cloud.google.com/storage/docs/authentication/canonical-requests#about-resource-path const url = new URL(req.url); const decoded = decodeURIComponent(url.pathname); const encoded = encodeURIComponent(decoded); const canonicalUri = encoded.replaceAll('%2F', '/');
const canonicalQuery = ''; const canonicalHeaders = `host:${HOST}\nx-goog-content-sha256:${EMPTY_HASH}\nx-goog-date:${timeStampISO8601Format}\n`;
return `${httpMethod}\n${canonicalUri}\n${canonicalQuery}\n${canonicalHeaders}\n${SIGNED_HEADERS}\n${EMPTY_HASH}`;}
// Task 2: Create a String to Sign// https://cloud.google.com/storage/docs/authentication/signatures#string-to-signfunction generateStringToSign(timeStampISO8601Format, YYYYMMDD, canonicalRequest) { const scope = `${YYYYMMDD}/${GCS_REGION}/${GCS_SERVICE}/goog4_request`; const hashedCanonicalRequest = crypto.SHA256(canonicalRequest);
return `GOOG4-HMAC-SHA256\n${timeStampISO8601Format}\n${scope}\n${hashedCanonicalRequest}`;}
// Task 3: Calculate Signature// https://cloud.google.com/storage/docs/authentication/signatures#signing-processfunction generateSignature(YYYYMMDD, stringToSign) { const round1 = hmacSha256('GOOG4' + SECRET, YYYYMMDD); const round2 = hmacSha256(round1, GCS_REGION); const round3 = hmacSha256(round2, GCS_SERVICE); const round4 = hmacSha256(round3, 'goog4_request');
return hmacSha256(round4, stringToSign);}
function removeQuery(req) { let url = new URL(req.url); url.search = new URLSearchParams();
return new Request(url.toJSON(), req);}
function hmacSha256(signingKey, stringToSign) { return crypto.HmacSHA256(stringToSign, signingKey, { asBytes: true });}
This page is part of a series in the Static content topic.
User contributed notes
BETADo you see an error in this page? Do have an interesting use case, example or edge case people should know about? Share your knowledge and help people who are reading this page! (Comments are moderated; for support, please contact support@fastly.com)