Azure blob storage bucket origin (private)

Use Microsoft Azure authenticated requests to protect communication between your Fastly service and Azure.

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:

  1. JavaScript
  2. Rust
package.json
JavaScript
{"dependencies":{"crypto-js":"^4.1.1"}}
index.js
JavaScript
/// <reference types="@fastly/js-compute" />
import * as crypto from "crypto-js";
// The name of your Azure Storage Account
const ACCOUNT_NAME = "computeatedgedemo";
// The Azure Storage Account shared key from your Azure Storage developer's account. It must be Base64 decoded.
const ACCOUNT_KEY = "XXXXXXXX";
// The backend domain name needs to be set as STORAGE_ACCOUNT_NAME.blob.core.windows.net
const HOST = "computeatedgedemo.blob.core.windows.net";
const BLOB_CONTAINER_NAME = "compute-at-edge-demo";
const MS_API_VERSION = "2017-11-09";
addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));
async function handleRequest(event) {
let req = fixupURL(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: "azure_backend",
});
// Remove MS headers returned from Azure Blob
res.headers.delete("x-ms-request-id");
res.headers.delete("x-ms-blob-type");
res.headers.delete("x-ms-server-encrypted");
res.headers.delete("x-ms-version");
res.headers.delete("x-ms-creation-time");
res.headers.delete("x-ms-lease-status");
res.headers.delete("x-ms-lease-state");
res.headers.delete("content-md5");
res.headers.delete("server");
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: "azure_backend",
});
} else {
return new Response("This method is not allowed", { status: 405 });
}
}
function authorizeRequest(req) {
// The timestamp format should be something like
// Fri, 01 Jan 2021 00:14:22 GMT
const timestamp = new Date().toUTCString();
// Decode base64 format access key to binary key
const decodedKey = crypto.enc.Base64.parse(ACCOUNT_KEY);
// Canonical headers must be sorted and concatenated with newlines.
// If there are query params there is a separate spec for that, not supported here yet
const canonicalizedHeaders = `x-ms-date:${timestamp}\nx-ms-version:${MS_API_VERSION}\n`;
// Canonical Resource is /account/cotainer/object
const url = new URL(req.url);
const canonicalizedResource = `/${ACCOUNT_NAME}${url.pathname}`;
console.log(`canonicalizedResource = ${canonicalizedResource}`);
// Construct everything properly before signing
// We are adding 4 newlines here, however the spec says we
// can override any of these 4 headers with values if we want.
// For now we are just blanking them out
const stringToSign = `GET\n\n\n\n${canonicalizedHeaders}${canonicalizedResource}`;
// HMAC-sign with SHA256 and Base64-encode the result
const signature_binary = hmacSha256(decodedKey, stringToSign);
const signature = crypto.enc.Base64.stringify(signature_binary);
const authorization = `SharedKeyLite ${ACCOUNT_NAME}:${signature}`;
req.headers.set("host", HOST);
req.headers.set("authorization", authorization);
req.headers.set("x-ms-date", timestamp);
req.headers.set("x-ms-version", MS_API_VERSION);
console.log(`Path: ${url.pathname}, Authorization: ${authorization}`);
}
function fixupURL(req) {
let url = new URL(req.url);
// Ignore the query string from client
url.search = "";
// Request path for azure storage is /container/object
url.pathname = `/${BLOB_CONTAINER_NAME}${url.pathname}`;
return new Request(url, req);
}
function hmacSha256(signingKey, stringToSign) {
return crypto.HmacSHA256(stringToSign, signingKey, { asBytes: true });
}
This page is part of a series in the Static content use case.