Signed URL for private buckets with CDN on Google Cloud don't work

605 Views Asked by At

I'm trying to read a private bucket file with a CDN applied via a signed URL.

I created two private buckets.

a-bucket
b-bucket

And to apply CDN to these buckets, I created a load balancer and added two backends to this load balancer.

a-backend
b-backend

And added host, path, backend.

a.example.com, /*, a-backend
b.example.com, /*, b-backend

I also added a sign key to each CDN backend.

a-sign-key
b-sign-key

And I added two domains in the hosting service to access the two buckets by domain. The destination ip is the same.

a.example.com
b.example.com

Finally, I created a signed URL using the KeyName and Key in NestJS.

The problem is that I was able to read a-bucket through a.example.com, but I couldn't read b-bucket through b.example.com.

AccessDenied

Anything I'm missing?

2

There are 2 best solutions below

2
Hyeonjun Jeong On

I solved the problem. I had to grant permission for Cloud CDN to access the bucket. The problem was that I gave permission to a-bucket, but not to b-bucket.

gsutil iam ch \
  serviceAccount:[email protected]:objectViewer \
  gs://BUCKETNAME
0
zd5151 On
from datetime import datetime, timedelta
import base64
import hashlib
import hmac
from urllib.parse import urlsplit, parse_qs, urlencode

def sign_url(base_url: str, path: str, key_name: str, base64_key: str, expiration_time: datetime) -> str:
    """Generates a signed URL for accessing a private resource with a specified expiration time.

    Args:
        base_url: The base URL (domain) of the resource, without scheme (http/https).
        path: The path to the resource on the base URL.
        key_name: The name of the signing key.
        base64_key: The signing key, base64 encoded.
        expiration_time: The expiration time for the signed URL.

    Returns:
        A signed URL with query parameters including the expiration time, key name, and signature.
    """
    # Ensure the path starts with '/'
    if not path.startswith('/'):
        path = '/' + path

    # Construct the full URL to be signed
    full_url = f"https://{base_url}{path}"

    # Parse the URL to prepare for signing
    parsed_url = urlsplit(full_url)
    query_params = parse_qs(parsed_url.query, keep_blank_values=True)

    # Convert expiration time to a UNIX timestamp (seconds since epoch)
    expiration_timestamp = int(expiration_time.timestamp())

    # Add 'Expires' and 'KeyName' to the query parameters
    query_params['Expires'] = str(expiration_timestamp)
    query_params['KeyName'] = key_name

    # Reconstruct the URL with the added query parameters for signing
    url_to_sign = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}?{urlencode(query_params, doseq=True)}"

    # Decode the base64-encoded key
    decoded_key = base64.urlsafe_b64decode(base64_key)

    # Create a signature using SHA-256
    digest = hmac.new(decoded_key, url_to_sign.encode("utf-8"), hashlib.sha256).digest()
    signature = base64.urlsafe_b64encode(digest).decode("utf-8")

    # Append the signature to the URL
    signed_url = f"{url_to_sign}&Signature={signature}"

    return signed_url

# Usage example with the provided information
if __name__ == "__main__":
    base_url = "some-domain.services"  # Load Balancer domain
    path = "/no-fetch/three-cats2.jpg"  # Path to the object in the CDN bucket
    key_name = "The_key_name"
    base64_key = "auto_generated_code"
    expiration_time = datetime.utcnow() + timedelta(hours=1)  # URL expires in 1 hour

    signed_url = sign_url(base_url, path, key_name, base64_key, expiration_time)
    print("Signed URL:", signed_url)