Skip to content

Instantly share code, notes, and snippets.

@jasonpolites
Last active November 13, 2017 21:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasonpolites/dfce01b168420e676823350da7a031b3 to your computer and use it in GitHub Desktop.
Save jasonpolites/dfce01b168420e676823350da7a031b3 to your computer and use it in GitHub Desktop.
Demo of manually signing a GCS url
/**
* Copyright 2017, Google, Inc.
* Licensed under the Apache License, Version 2.0 (the "License")
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
const rp = require('request-promise');
/**
* @param {string} objectRef The Canonicalized Resource Reference for the object in GCS (e.g. /bucket/objectname).
* @param {number} ttlSeconds The number of seconds from `now` this link should remain valid (e.g 60).
* @param {string} contentType (Optional) The content type of the object (provide only if the client sends a content-type header in the request).
*/
exports.signUrlForUpload = (objectRef, ttlSeconds, contentType) => {
return signUrl('PUT', objectRef, ttlSeconds, contentType);
}
/**
* @param {string} objectRef The Canonicalized Resource Reference for the object in GCS (e.g. /bucket/objectname).
* @param {number} ttlSeconds The number of seconds from `now` this link should remain valid (e.g 60).
* @param {string} contentType (Optional) The content type of the object (provide only if the client sends a content-type header in the request).
*/
exports.signUrlForDownload = (objectRef, ttlSeconds, contentType) => {
return signUrl('GET', objectRef, ttlSeconds, contentType);
}
function signUrl(method, objectRef, ttlSeconds, contentType) {
let now = parseInt(Date.now() / 1000); // ms to seconds
let expires = now + parseInt(ttlSeconds);
let stringToSign = makeSignedUrlString(method, objectRef, expires, contentType);
console.log(`String to sign: ${stringToSign}`);
return signUrlString(stringToSign).then((value) => {
console.log(`Got signed blob payload: ${JSON.stringify(value)}`);
let signature = encodeURIComponent(value.signedBlob.signature);
let baseUrl = `https://storage.googleapis.com${objectRef}?GoogleAccessId=${value.email}&Expires=${expires}&Signature=${signature}`;
console.log(`Base url: ${baseUrl}`);
return Promise.resolve(baseUrl);
});
}
function makeSignedUrlString(method, object, expires, contentType) {
contentType = contentType || '';
// TODO: These probably should be params to the fn
let md5 = '';
// StringToSign = HTTP_Verb + "\n" +
// Content_MD5 + "\n" +
// Content_Type + "\n" +
// Expiration + "\n" +
// Canonicalized_Extension_Headers + "\n" +
// Canonicalized_Resource
let stringToSign = `${method}\n${md5}\n${contentType}\n${expires}\n${object}`;
console.log(`String to sign (pre base64): [${stringToSign}]`);
return new Buffer(stringToSign).toString('base64');
}
function signUrlString(urlString) {
let serviceNamePromise = callMetadataService('email');
let serviceTokenPromise = callMetadataService('token');
let email;
return Promise.all([
serviceNamePromise,
serviceTokenPromise])
.then(([serviceAccountName, serviceAccountToken]) => {
let projectId = process.env.GCLOUD_PROJECT;
email = serviceAccountName;
let accessToken = JSON.parse(serviceAccountToken).access_token;
return signBlob(urlString, projectId, email, accessToken);
}).then((signedBlob) => {
return Promise.resolve({
'email' : email,
'signedBlob': signedBlob
});
});
}
function callMetadataService(endpoint) {
return rp({
uri: `http://metadata/computeMetadata/v1/instance/service-accounts/default/${endpoint}`,
headers: {
'Metadata-Flavor': 'Google'
}
});
}
function signBlob(blob, projectId, serviceAccount, accessToken) {
let uri = `https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts/${serviceAccount}:signBlob`;
let bearerToken = `Bearer ${accessToken}`;
return rp({
method: 'POST',
uri: uri,
headers: {
'Authorization': bearerToken
},
body: {
'bytesToSign': blob
},
json: true
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment