I can't get a signed URL working with a URLPrefix for Google Cload CDN.
I've setup a bucket which is a backend bucket to my Cloud CDN instance. I've successfully setup a URL signing key and have produced a working signed URL for a specific path all use the instruction found at https://cloud.google.com/cdn/docs/using-signed-urls?hl=en_US
Using my signCdnUrl2 function below I can produce a working signed url for a specific resource e.g.
https://example.com/foo.mp4?Expires=[EXPIRATION]&KeyName=[KEY_NAME]&Signature=[SIGNATURE]
export function signCdnUrl2(fileName: string, opts: SignedUrlOptions, urlPrefix?: string) {
const expireVal = '' + new Date().getTime() + opts.expires;
const urlToSign = `${opts.baseUrl}/${fileName}?Expires=${expireVal}&KeyName=${opts.keyName}`;
// Compute signature
const keyBuffer = Buffer.from(opts.keyBase64, 'base64');
let signature = createHmac('sha1', keyBuffer).update(urlToSign).digest('base64');
signature = Base64urlUtil.escape(signature);
// Add signature to urlToSign and return signedUrl
return urlToSign + `&Signature=${signature}`;
}
I want to avoid "the need to create a new signature for each distinct URL" so I'm following instructions at https://cloud.google.com/cdn/docs/using-signed-urls?hl=en_US#url-prefix to add the URL Prefix option.
I'm unable to successfully produce a working signed url with a prefix. My currrent attempt is below
export function signCdnUrl3(fileName: string, opts: SignedUrlOptions, urlPrefix?: string) {
const expireVal = '' + new Date().getTime() + opts.expires;
const urlPrefixCombined = `${opts.baseUrl}${urlPrefix}`;
// UrlPrefix param if provided otherwise empty string
const urlPrefixEncoded = urlPrefix ? Base64urlUtil.encode(urlPrefixCombined) : '';
// Param string to be signed with key
const paramsToSign = `URLPrefix=${urlPrefixEncoded}&Expires=${expireVal}&KeyName=${opts.keyName}`;
// Compute signature
const keyBuffer = Buffer.from(opts.keyBase64, 'base64');
let signature = createHmac('sha1', keyBuffer).update(paramsToSign).digest('base64');
signature = Base64urlUtil.escape(signature);
// Add signature to url
return `${opts.baseUrl}/${fileName}?${paramsToSign}&Signature=${signature}`;
}
I get a 403 response from cloud cdn if I try and access any resource under the given prefix in the case the root of the bucket
Log entry from the load balancer shows it's detecting it as an invalid signature
Is there something I'm interpreting wrong in the instructions or have I just missed something in my implementation? Any guidance would be appreciated.
Added Base64Util code for completeness
export class Base64urlUtil {
public static encode(str: string, encoding: any = 'utf8'): string {
const buffer: Buffer = Buffer.from(str, encoding);
const encodedStr: string = buffer.toString('base64');
const final: string = Base64urlUtil.escape(encodedStr);
return final;
}
public static decode(str: string, encoding?: string): string {
return Buffer.from(Base64urlUtil.unescape(str), 'base64').toString(encoding || 'utf8');
}
public static escape(str: string): string {
return str.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
public static unescape(str: string): string {
return (str + '==='.slice((str.length + 3) % 4))
.replace(/-/g, '+')
.replace(/_/g, '/');
}
}
Update
Using the implementation provided by @elithrar https://stackoverflow.com/a/61315372/4330441 I swapped out the his sample values in signedParams for my own live values.
let signedParams = signURLPrefix(
"https://<my-server>/sample/360p/",
1588291200,
"<my-key>",
"<valid-key>"
)
The result was so:
URLPrefix=aHR0cHM6Ly9zcHluYWwucmNmc29mdHdhcmUuaW8vc2FtcGxlLzM2MHAv&Expires=1588291200&KeyName=my-key-name&Signature=wrbOloT+m31ZnQZei2Csqq0XaGY=
When I then append these query params to call the cloud cdn endpoint at this address:
I get the same 403 response and the matching invalid signature in the cdn logs
Attempted with two different signing keys which have worked fine for signing single specific urls without the url prefix.