How to Access a Private GCS Bucket Using a Load Balancer
Serve a private GCS bucket through an HTTPS load balancer that signs its requests with a service account's HMAC key.
Lab · Step through it
Open in the Lab →- Step 01 · Console · Cloud Storage Private
Lock the bucket
Create the bucket with public access prevention and uniform access control, then upload a file. Nobody outside the project can read it.
Public access prevention: EnforcedAccess control: UniformObject: image.jpg - Step 02 · Console · IAM and Cloud Storage Key ready
Give a service account a key
Create a service account, give it an HMAC key under Cloud Storage → Settings → Interoperability, and grant it Storage Legacy Object Reader on the bucket.
Service account: PRIVATE_GCS_SAHMAC key: HMAC_ACCESS_KEY + HMAC_SECRETBucket role: Storage Legacy Object Reader - Step 03 · Console · Network endpoint groups Backend ready
Point a NEG at the bucket
An internet network endpoint group gives the load balancer a backend outside the VPC: the bucket’s own hostname on port 443.
Name: private-gcs-negType: Internet NEG (Global)Endpoint: [BUCKET_NAME].storage.googleapis.comDefault port: 443 - Step 04 · Console · Load balancing 403 Forbidden
Put a load balancer in front
An HTTPS load balancer with a static IP and certificate, the NEG as backend, Cloud CDN on, and a Host header set to the bucket’s hostname. Requests now reach the bucket, unsigned.
Frontend: HTTPS, static IP, certificateBackend: Internet NEG (private-gcs-neg), HTTPSCloud CDN: enabledHeader: host: [BUCKET_NAME].storage.googleapis.com - Step 05 · Cloud Shell 403 Forbidden
Export the backend service
Private origin authentication is not in the console. Export the backend service to YAML and add the HMAC key under securitySettings.
Terminal window gcloud compute backend-services export --global [Backend Service Name] \--destination=private-gcs.yaml \--project [PROJECT_ID] - Step 06 · private-gcs.yaml 403 Forbidden
Add the HMAC key
awsV4Authentication tells the load balancer to sign each request to the bucket with the service account’s key. Nothing changes until it is imported.
securitySettings:awsV4Authentication:accessKeyId: [HMAC_ACCESS_KEY]accessKey: [HMAC_SECRET]originRegion: [REGION] - Step 07 · Cloud Shell Signing
Import it
Import the edited file. From now on every request the load balancer sends to the bucket is signed.
Terminal window gcloud compute backend-services import --global [Backend Service Name] \--source=private-gcs.yaml \--project [PROJECT_ID] - Step 08 · Any terminal 200 OK
Prove it
Ask the load balancer for the file. The bucket is still private, but the signed request gets 200 OK.
Terminal window curl -X GET -I https://[LOAD_BALANCER_IP]/image.jpgHTTP/1.1 200 OKx-goog-storage-class: STANDARDvia: 1.1 google
Follow these steps to create a private Google Cloud Storage (GCS) bucket and configure access through a load balancer.
Create a Private GCS Bucket
- Create a GCS Bucket: Make sure it’s private by selecting Enforce public access prevention on this bucket and choose Uniform for Access control.
- Upload Your Content: Add your files to the bucket, for example
image.jpg.
Create a Service Account
- Navigate to IAM & Admin -> Service Accounts.
- Create a Service Account: Add a name for the service account (referred to as PRIVATE_GCS_SA) and leave other settings as default.
Generate HMAC for the Service Account
- Go to Cloud Storage -> Settings.
- Switch to the Interoperability Tab.
- Create an Access Key: Under “Access keys for service accounts,” click
+ Create a key for a Service Account. - Select the Service Account: Choose the previously created service account (PRIVATE_GCS_SA) and click
Create Key. - Save the Access Key and Secret: Note the “Access key” (HMAC_ACCESS_KEY) and “Secret” (HMAC_SECRET) for later use.
Add Service Account Permissions to the Bucket
- Go to Cloud Storage -> [BUCKET_NAME].
- Switch to the PERMISSIONS Tab.
- Grant Access: Click
+ GRANT ACCESSand type the service account email (PRIVATE_GCS_SA). Assign the “Storage Legacy Object Reader” role and clickSave.
Create Network Endpoint Groups
- Navigate to Compute Engine -> Network Endpoint Groups.
- Create a Network Endpoint Group: Name it like “private-gcs-neg”
- Configure the Endpoint Group:
- Type: Select “Internet NEG (Global, Regional).”
- Default Port: 443.
- Add Through: Choose “Fully qualified domain name and port.”
- Domain Name: Input “[BUCKET_NAME].storage.googleapis.com.”
- Create the Endpoint Group.
Create an HTTP(S) Load Balancer
- Go to Network Services -> Load Balancing.
- Create a Load Balancer: Click
+ Create Load Balancer. - Configure the Load Balancer:
- Type: Select Application Load Balancer (HTTP/HTTPS).
- Frontend Configuration:
- Protocol: HTTPS (includes HTTP/2 and HTTP/3).
- IP Address: Static IP address (or create a new one).
- Certificate: Select or create a new one.
- HTTP to HTTPS Redirect: Enable it.
- Backend Configuration:
- Create a Backend Service: Name it.
- Backend Type: Select Internet network endpoint group.
- Protocol: HTTPS.
- Backend Selection: Choose the previously created Network Endpoint Group.
- Cloud CDN: Ensure Cloud CDN is enabled.
- Security: Select None for Cloud Armor backend security policy.
- Custom Request Headers: Add a header with “host” as the name and “[BUCKET_NAME].storage.googleapis.com” as the value.
- Create the Load Balancer: Set the name and finalize the creation.
Configure Private Origin Authentication
- Open Cloud Shell or gcloud CLI.
- Export Backend Configuration: Use the command:
Terminal window gcloud compute backend-services export --global [Backend Service Name] \--destination=private-gcs.yaml \--project [PROJECT_ID] - Edit the YAML File: Add the following to the
securitySettingssection:ReplacesecuritySettings:awsV4Authentication:accessKeyId: [HMAC_ACCESS_KEY]accessKey: [HMAC_SECRET]originRegion: [REGION][HMAC_ACCESS_KEY],[HMAC_SECRET], and[REGION]with the appropriate values. - Import the Configuration: Use the command:
Terminal window gcloud compute backend-services import --global [Backend Service Name] \--source=private-gcs.yaml \--project [PROJECT_ID]
Testing
Run the following cURL command
curl -X GET -I https://[LOAD_BALANCER_IP]/image.jpgIF success, it will return 200 similar like this
HTTP/1.1 200 OKx-goog-generation: 1721373786382023x-goog-metageneration: 1x-goog-stored-content-encoding: identityx-goog-stored-content-length: 1456x-amz-meta-x-goog-reserved-source-generation: 1721283240953192x-goog-hash: crc32c=Bbj32A==x-goog-hash: md5=Gz8lupMp0rjzxJSMOWfbhQ==x-amz-checksum-crc32c: Bbj32A==x-goog-storage-class: STANDARDaccept-ranges: bytesContent-Length: 1456x-guploader-uploadid: ACJd0NqW9GOPmKlURsNWyGpDyxgA34g-BB9psFcQFai8XdB52HuwBbDEiO4B3ZJPOB91_ROPluEserver: UploadServeralt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000via: 1.1 googleDate: Sat, 20 Jul 2024 06:37:12 GMTCache-Control: public,max-age=3600Age: 17Last-Modified: Fri, 19 Jul 2024 07:23:06 GMTETag: "1b3f25ba9329d2b8f3c4948c3967db85"Content-Type: image/pngAlt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000