Creating signed URLs for Google Cloud Storage using NodeJS

Viewed 13026

I'm trying to create a signature for a privately stored file in Google Cloud Storage; so that I can distribute a time-limited link.

Currently doing this and it makes a signature that's too short ... where am I going wrong?

var crypto = require("crypto");

var ttl = new Date().getTime() + 3600;
var id = 'the_target_file.txt';
var bucketName = 'bucket_name';
var POLICY_JSON = "GET\n" + "\n" + "\n" + ttl + "\n" + '/' + bucketName + '/' + id;

// stringify and encode the policy
var stringPolicy = JSON.stringify(POLICY_JSON);
var base64Policy = Buffer(stringPolicy, "utf-8").toString("base64");

// sign the base64 encoded policy
var privateKey = "MY_PRIVATE_KEY";
var sha256 = crypto.createHmac("sha256", privateKey);
var signature = sha256.update(new Buffer(base64Policy, "utf-8")).digest("base64");

console.log ( signature );
4 Answers

Assuming this question is to sign the CDN url backed by google bucket backend, here what works for me (code above did not work for me).

Opts and signing function calling:

const signUrlOptions = {
  expires: '' + new Date().getTime() + 3600, // one hour
  keyName: '_SIGNING_KEY_NAME_', // URL signing key name (the one one you created in the CDN backend bucket)
  keyBase64: '_SIGNING_KEY_BASE64_', // the URL signing key base64 content (base64-encoded, 128-bit value, ~24 characters)
  baseUrl: '_CDN_BASE_URL_' // your base CDN URL (can be IP http://123.... when dev env or https://cdn_dns_name or https dns name)
}

const signedUrl = signCdnUrl('demo.png', signedUrlOptions);

signing function:

import { createHmac } from 'crypto';

const BASE64_REPLACE = { '+': '-', '/': '_', '=': '' };

export function signCdnUrl(fileName, opts) {
  // URL to sign
  const urlToSign = `${opts.baseUrl}/${fileName}?Expires=${opts.expires}&KeyName=${opts.keyName}`;

  // Compute signature
  const keyBuffer = Buffer.from(opts.keyBase64, 'base64');
  let signature = createHmac('sha1', keyBuffer).update(urlToSign).digest('base64');
  signature = signature.replace(/[+/=]/g, c => (<any>BASE64_REPLACE)[c]); // might be a better way

  // Add signature to urlToSign and return signedUrl
  return urlToSign + `&Signature=${signature}`;
}

Hope this helps. Somehow google cloud doc does not have a nodejs example and the file.getSignedUrl() add confusion to the mix as it does not seem to be related to CDN URL signing.

Note:

Note: Probably want to move base64 -> buffer work to the caller as opts.keyBuffer

If nodejs @google-cloud/storage library is already part of your project then best way is to use it. Below code is by google storage sdk docs for nodejs Link here

npm install @google-cloud/storage

function main(bucketName = 'you_bucket_name', filename = 'your_file_path_without_bucket_name') {
    const {Storage} = require('@google-cloud/storage');

    // Creates a client (Parameters not required if you are already in GCP environment)
    const storage = new Storage({
        projectId: 'your_project_id',
        keyFilename: './json_key_path_for_some_service_account.json'
    });

    async function generateV4ReadSignedUrl() {
        // These options will allow temporary read access to the file
        const options = {
            version: 'v4',
            action: 'read',
            expires: Date.now() + 15 * 60 * 1000, // 15 minutes
        };

        // Get a v4 signed URL for reading the file
        const [url] = await storage
            .bucket(bucketName)
            .file(filename)
            .getSignedUrl(options);

        console.log('Generated GET signed URL:');
        console.log(url);
        console.log('You can use this URL with any user agent, for example:');
        console.log(`curl '${url}'`);
    }

    generateV4ReadSignedUrl().catch(console.error);
    // [END storage_generate_signed_url_v4]
}
main(...process.argv.slice(2));
Related