# Fastly VCL: AWS SigV4 (Query String / Presigned URL) for S3 GET
# Signs the origin request by moving auth from headers into X-Amz-* query params.

declare local var.awsAccessKey STRING;
declare local var.awsSecretKey STRING;
declare local var.awsS3Bucket STRING;
declare local var.awsRegion STRING;
declare local var.awsS3Host STRING;
declare local var.dateStamp STRING;
declare local var.amzDate STRING;
declare local var.scope STRING;
declare local var.signedHeaders STRING;
declare local var.canonicalHeaders STRING;
declare local var.canonicalQuery STRING;
declare local var.canonicalRequest STRING;
declare local var.stringToSign STRING;
declare local var.signature STRING;
declare local var.credentialScope STRING;
declare local var.expires STRING;         # seconds, max 604800 (7 days)

# Demo creds (do NOT use in production)
set var.awsAccessKey = "AKIAIYV3R5KWHXJKD4QQ";
set var.awsSecretKey = "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";
set var.awsS3Bucket = "demo-s3-fiddle-origin";
set var.awsRegion   = "us-east-2";
set var.awsS3Host   = var.awsS3Bucket + ".s3." + var.awsRegion + ".amazonaws.com";

if (req.method == "GET" && !req.backend.is_shield) {
  # Prepare basic values
  set bereq.http.host = var.awsS3Host;
  set var.amzDate     = strftime({"%Y%m%dT%H%M%SZ"}, now);
  set var.dateStamp   = strftime({"%Y%m%d"}, now);
  set var.scope       = var.dateStamp + "/" + var.awsRegion + "/s3/aws4_request";
  set var.signedHeaders = "host";
  set var.expires = "300"; # 5 minutes; adjust as needed (<= 604800)

  # Normalize path and strip any incoming qs (we’ll build our own)
  set bereq.url = querystring.remove(bereq.url);
  set bereq.url = regsuball(urlencode(urldecode(bereq.url.path)), {"%2F"}, "/");

  # Canonical headers (only those you’ll list in SignedHeaders)
  set var.canonicalHeaders = "host:" + bereq.http.host + LF;

  # Build the canonical query string (names & values URI-encoded, then
  # keys sorted ASCII). We control the order by writing them already sorted:
  # X-Amz-Algorithm, X-Amz-Credential, X-Amz-Date, X-Amz-Expires,
  # (X-Amz-Security-Token if present), X-Amz-SignedHeaders
  set var.credentialScope = var.awsAccessKey + "/" + var.scope;

  set var.canonicalQuery = ""
    + "X-Amz-Algorithm=" + "AWS4-HMAC-SHA256"
    + "&X-Amz-Credential=" + urlencode(var.credentialScope)
    + "&X-Amz-Date=" + urlencode(var.amzDate)
    + "&X-Amz-Expires=" + urlencode(var.expires)
    + "&X-Amz-SignedHeaders=" + urlencode(var.signedHeaders);

  # For presigned URLs to S3, the payload hash is the literal string
  # "UNSIGNED-PAYLOAD" in the canonical request. (Do NOT add x-amz-content-sha256
  # to the query; it isn’t required for query auth.)
  set var.canonicalRequest = ""
    + "GET" + LF
    + bereq.url.path + LF
    + var.canonicalQuery + LF
    + var.canonicalHeaders + LF
    + var.signedHeaders + LF
    + "UNSIGNED-PAYLOAD";

  # Build StringToSign and compute signature
  set var.stringToSign = ""
    + "AWS4-HMAC-SHA256" + LF
    + var.amzDate + LF
    + var.scope + LF
    + std.replace_prefix(digest.hash_sha256(var.canonicalRequest), "0x", "");

  set var.signature = digest.awsv4_hmac(
      var.awsSecretKey,   # secret
      var.dateStamp,      # yyyymmdd
      var.awsRegion,      # region
      "s3",               # service
      var.stringToSign);

  # Finalize the URL
    set bereq.url = querystring.add(bereq.url, "X-Amz-Algorithm", "AWS4-HMAC-SHA256");
    set bereq.url = querystring.add(bereq.url, "X-Amz-Credential", var.awsAccessKey + "/" + var.scope);
    set bereq.url = querystring.add(bereq.url, "X-Amz-Date", var.amzDate);
    set bereq.url = querystring.add(bereq.url, "X-Amz-Expires", var.expires);
    set bereq.url = querystring.add(bereq.url, "X-Amz-SignedHeaders", var.signedHeaders);
    set bereq.url = querystring.add(bereq.url, "X-Amz-Signature", std.replace_prefix(var.signature,"0x", ""));

  # Remove client headers that could leak
  unset bereq.http.Accept;
  unset bereq.http.Accept-Language;
  unset bereq.http.User-Agent;
  unset bereq.http.Fastly-Client-IP;
}