Azure blob storage bucket origin (private)

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


Use this solution in your VCL service (click RUN below to test this solution or clone it to make changes):


Use this solution in your Compute@Edge service:

  1. Rust
  2. JavaScript
anyhow = "1.0.28"
base64 = "0.13"
chrono = "0.4"
fastly = "0.7.0"
hex = "0.4"
hmac-sha256 = "0.1"
log = "0.4"
log-fastly = "0.2"
/// AZURE Backend Name
/// The backend domain name needs to be set set as
/// For more details, check
const BACKEND_NAME_AZURE: &str = "";
const MS_API_VERSION: &str = "2017-11-09";
/// Holds Azure Blob storage access information
struct AzureConfig {
account_key: String,
account_name: String,
blob_container: String,
impl AzureConfig {
/// Load the Azure configuration.
/// This assumes an Edge Dictionary named "azure_config" is attached to this service,
/// with entries for the account name, key, and blob container.
fn load_config() -> Self {
let dict = Dictionary::open("azure_config");
Self {
account_key: dict.get("account_key").expect("account key configured"),
account_name: dict.get("account_name").expect("account name configured"),
blob_container: dict.get("blob_container").expect("account blob configured"),
fn main(mut req: Request) -> Result<Response, Error> {
log_fastly::init_simple("my_log", log::LevelFilter::Info);
let azure_config = AzureConfig::load_config();
// Only generate authorize header for GET and HEAD request
// Pass PURGE to backend as it is
// Block other kinds of method
let resp = match req.get_method() {
&Method::GET | &Method::HEAD => {
req = authorize_azure_storage_request(
m if m == "PURGE" => {
// url of cached object has no query string, and has blob container added to path
// so when do the purge, we need to make sure the url matches
_ => Response::from_status(StatusCode::METHOD_NOT_ALLOWED),
/// Generate Authorization headers of the Azure request
fn authorize_azure_storage_request(
mut req: Request,
storage_access_key: &str,
storage_account_name: &str,
blob_container: &str,
) -> Result<Request> {
// The date format should be something like
// Fri, 01 Jan 2021 00:14:22 GMT
let ms_date = chrono::Utc::now().format("%a, %d %b %Y %T GMT").to_string();
// Decode base64 format access key to binary key
let access_key_decoded = base64::decode(storage_access_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
let canonical_headers = format!("x-ms-date:{}\nx-ms-version:{}\n", ms_date, MS_API_VERSION);
// Canonical Resource is /ACCOUNT/CONTAINER/FILE
let canonical_resource = format!(
// 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
let string_to_sign = format!("GET\n\n\n\n{}{}", canonical_headers, canonical_resource);
// HMAC-sign with SHA256 and Base64-encode the result
let signature_binary = HMAC::mac(string_to_sign.as_bytes(), &access_key_decoded);
let signature = base64::encode(signature_binary);
let authorization = format!("SharedKeyLite {}:{}", storage_account_name, signature);
// request path for azure storage is /containder/object_id
req.set_path(&format!("/{}{}", blob_container, req.get_path()));
// For the the backend request method as GET
// Add authorization related headers to request
req.set_header(header::AUTHORIZATION, &authorization);
req.set_header("x-ms-date", &ms_date);
req.set_header("x-ms-version", MS_API_VERSION);
log::info!("Path: {}, Authorization: {}", req.get_path(), authorization);
This page is part of a series in the Static content topic.