Search code examples
google-cloud-platformgoogle-cloud-cdn

Google Cloud CDN signedurl using URLPrefix


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

403 Response

Log entry from the load balancer shows it's detecting it as an invalid signature

Load Balancer Log

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:

https://my-server/sample/360p/video.mp4?URLPrefix=aHR0cHM6Ly9zcHluYWwucmNmc29mdHdhcmUuaW8vc2FtcGxlLzM2MHAv&Expires=1588291200&KeyName=my-key-name&Signature=wrbOloT+m31ZnQZei2Csqq0XaGY=

I get the same 403 response and the matching invalid signature in the cdn logs

CDN log

Attempted with two different signing keys which have worked fine for signing single specific urls without the url prefix.


Solution

  • I have same problem about it. Signed url with URLPrefix and signed cookie both do not work. I had tried to implement with Golang/Ruby, and I'm sure the signed logic is same with Golang example.

    After asked Google Support, they said: "As this feature was recently moved to GA, they found that it wasn't correctly enabled on your project. A fix is being implemented to address this issue and I expect it to be completely rolled out by next week. I will update you once the fix has been applied." I think it was also not correctly enabled on your project.

    I will try again and update information once the fix has been applied next week.

    Update

    We receive the latest response from Google Support that our project had enabled the feature. My code works without any modify.