← Back to Architectures
Advanced Level
Interactive
GCP Multi-Tenant Storage Architecture
Secure multi-tenant data isolation using Google Cloud Storage with IAM conditions and VPC Service Controls
Services
Cloud Storage IAM VPC-SC Cloud SQL
Use Case
Multi-Tenant SaaS on Google Cloud
☁️
GCP Multi-Tenant Blob Storage
Cloud Storage · Cloud CDN · Identity Platform · IAM Conditions · Per-Client Isolation
Cloud Storage Prefix IsolationCloud CDN + Signed URLsIdentity Platform (Firebase)Cloud Functions / Cloud RunCloud KMS Per-TenantCloud Armor WAF
AWS → GCP SERVICE MAPPING
Amazon S3→Cloud Storage (GCS)
CloudFront + OAC→Cloud CDN + Signed URLs
Cognito User Pool→Identity Platform (Firebase Auth)
Lambda@Edge→Cloud Functions / Cloud Run
API Gateway→Cloud Endpoints / Apigee / API Gateway
WAF + Shield→Cloud Armor
KMS CMK→Cloud KMS (CMEK)
DynamoDB→Firestore
CloudTrail→Cloud Audit Logs
CloudWatch→Cloud Monitoring + Cloud Logging
EventBridge → Lambda→Eventarc → Cloud Functions
STS Session Tags→IAM Conditions + Workforce Identity
Cloud CDN Signed URLs with V4 signing grant time-limited, tenant-scoped access. Cloud Functions validates the JWT, extracts the tenant ID, and generates V4 signed URLs pointing to the tenant's GCS prefix. Clients access data exclusively through the CDN — never directly from GCS.
1
Client / Tenant LayerCLIENT
Tenant A — Acme Corp
tenant_id: acme-001
📊 Dashboards, reports, media
🔒 /tenants/acme-001/* only
🔒 /tenants/acme-001/* only
CLIENT
Tenant B — Globex Inc
tenant_id: globex-002
📄 Documents, contracts, uploads
🔒 /tenants/globex-002/* only
🔒 /tenants/globex-002/* only
CLIENT
Tenant C — Initech
tenant_id: initech-003
🖼️ Images, user content
🔒 /tenants/initech-003/* only
🔒 /tenants/initech-003/* only
Client SDKs
Web (Firebase JS SDK)Mobile (Firebase iOS/Android)Python (google-cloud-storage)
Auth via Identity Platform → JWT with tenant_id custom claim.
JWT + tenant_id
2
Authentication & Tenant IdentityIDP
Identity Platform
Firebase Auth (Multi-Tenant)
Multi-Tenancy Mode EnabledCustom Claims: tenant_idBlocking Functions (pre-auth)MFA: TOTP / SMSSAML / OIDC Federation
Identity Platform's native multi-tenancy creates isolated tenant containers. Blocking Functions inject custom claims into the JWT before token issuance.
Token Structure
{
"sub": "user-uid-abc123",
"firebase": {
"tenant": "acme-001",
"sign_in_provider": "password"
},
"tenant_id": "acme-001",
"tenant_plan": "enterprise",
"role": "tenant-admin",
"iss": "https://securetoken.google.com/
PROJECT_ID",
"exp": 1738500000
}Workforce Identity Federation
Short-Lived GCP Credentials
Workforce Pool: tenant-poolProvider: Identity PlatformAttribute Mapping: tenant_idIAM Condition Binding
Maps JWT tenant_id to
attribute.tenant_id in IAM conditions. Short-lived tokens scoped to tenant's GCS prefix.Tenant Registry (Firestore)
Metadata Store
doctenants/acme-001
planenterprise
storage_quota500 GB
kms_keyprojects/.../keys/acme
cdn_key_namecdn-key-acme
created2024-11-20
Cloud CDN
3
Cloud CDN, Load Balancer & Edge SecurityGLB
Global HTTP(S) Load Balancer
+ Cloud CDN Backend
BackendGCS Bucket
ProtocolHTTPS (managed cert)
CDNEnabled (cache mode)
Domaincdn.example.com
SSL PolicyTLS 1.2+ (MODERN)
LoggingEnabled → Cloud Logging
GCS backend bucket with Cloud CDN enabled. Signed URL keys configured on the backend. No direct GCS access for clients.
EDGE
Cloud Functions — Auth Middleware
Tenant Validation + Signed URL Gen
# Cloud Function: Signed URL generator
from google.cloud import storage
from datetime import timedelta
import google.auth
def generate_signed_url(request):
# 1. Verify Firebase JWT
token = request.headers.get(
'Authorization', '').split(' ')[-1]
decoded = verify_firebase_token(token)
tenant = decoded['tenant_id']
# 2. Build tenant-scoped path
blob_name = request.args.get('key')
full_path = f'tenants/{tenant}/{blob_name}'
# 3. Block path traversal
if '..' in full_path:
return ('Forbidden', 403)
# 4. Generate V4 signed URL
client = storage.Client()
bucket = client.bucket('app-data-prod')
blob = bucket.blob(full_path)
url = blob.generate_signed_url(
version='v4',
expiration=timedelta(minutes=15),
method='GET'
)
return {'url': url}CDN Signed URL Flow
1
Client authenticates → Firebase JWT2
Cloud Function validates + extracts tenant3
Generates V4 signed URL for tenant prefix4
CDN serves cached content (signed key)5
GCS origin fetch if cache missCDN Signed URL Key (Ed25519)
WAF
Cloud Armor
WAF + DDoS Protection
Rate limiting (per-client IP)OWASP CRS 3.3 rulesGeo-restriction policiesBot managementAdaptive Protection (ML)Named IP lists (Tor etc)
Security policy attached to Load Balancer. Custom rules for per-tenant rate limiting using header-based matching.
Backend Bucket
4
Cloud Storage — Multi-Tenant BucketGCS
Bucket Structure
Prefix-Per-Tenant Layout
gs://app-data-prod/
├── tenants/acme-001/
│ ├── documents/
│ ├── media/
│ ├── exports/
│ └── uploads/
├── tenants/globex-002/
│ ├── documents/
│ └── uploads/
├── tenants/initech-003/
│ └── ...
└── _system/
IAM
IAM Condition (Tenant-Scoped)
# IAM binding with condition
resource "google_storage_bucket_iam_member" "tenant" {
bucket = "app-data-prod"
role = "roles/storage.objectViewer"
member = "principalSet://iam.googleapis.com/
locations/global/workforcePools/
tenant-pool/attribute.tenant_id/
acme-001"
condition {
title = "tenant-prefix-only"
expression = <<-EOT
resource.name.startsWith(
"projects/_/buckets/app-data-prod/
objects/tenants/acme-001/")
EOT
}
}
# Generic policy (dynamic via attribute)
# Uses resource.name condition to match
# the tenant_id attribute from the tokenIAM Conditions + resource.name matching = per-tenant enforcement without hardcoding.
Uniform Bucket-Level Access
# Bucket configuration
resource "google_storage_bucket" "data" {
name = "app-data-prod"
location = "US"
uniform_bucket_level_access = true
versioning { enabled = true }
lifecycle_rule {
condition { age = 90 }
action {
type = "SetStorageClass"
storage_class = "NEARLINE"
}
}
lifecycle_rule {
condition { age = 365 }
action {
type = "SetStorageClass"
storage_class = "COLDLINE"
}
}
encryption {
default_kms_key_name = google_kms_crypto_key
.default.id
}
logging {
log_bucket = "app-data-access-logs"
}
}GCS Configuration
Access ControlUniform (IAM only)
VersioningEnabled
EncryptionCMEK (per-tenant key)
Public AccessPrevention: Enforced
RetentionPolicy (optional)
LifecycleNearline@90d · Coldline@365d
Logging→ access log bucket
ReplicationDual-region / multi-region
CMEK
Per-Tenant Cloud KMS (CMEK)
acme-001projects/.../keys/acme
globex-002projects/.../keys/globex
initech-003projects/.../keys/initech
Cloud KMS key ring per region. Per-tenant key with IAM binding restricted to tenant's service account. Automatic rotation (90 days).
Cloud EKM (external) optional
PERIMETER
VPC Service Controls
Service Perimeter: storage.googleapis.comAccess Level: Corp network + identityIngress: Cloud Functions onlyEgress: Deny all except CDN
VPC-SC creates a security perimeter around GCS. Prevents data exfiltration even if IAM is misconfigured. Ingress rules allow only authorized services.
API
Audit
5
Backend API LayerCloud Endpoints / API Gateway
Tenant-Scoped Routes
POST /blobs/upload → signed upload URL
GET /blobs/{key} → signed download URL
GET /blobs → list tenant objects
DEL /blobs/{key} → soft delete (version)
ESP (Extensible Service Proxy) validates Firebase JWT. Cloud Run backend injects tenant prefix. API quota per tenant via API keys.
UPLOAD
Upload (V4 Signed URL)
from google.cloud import storage
from datetime import timedelta
def upload_handler(request):
tenant = verify_jwt(request)['tenant_id']
filename = request.args['filename']
key = f'tenants/{tenant}/uploads/{filename}'
client = storage.Client()
bucket = client.bucket('app-data-prod')
blob = bucket.blob(key)
# Set per-tenant CMEK
blob.kms_key_name = get_tenant_key(tenant)
url = blob.generate_signed_url(
version='v4',
expiration=timedelta(minutes=5),
method='PUT',
content_type='application/octet-stream'
)
return {'url': url, 'key': key}DOWNLOAD
Download (CDN Signed URL)
import datetime, hashlib, hmac, base64
def download_handler(request):
tenant = verify_jwt(request)['tenant_id']
blob_key = request.args['key']
full = f'tenants/{tenant}/{blob_key}'
# CDN Signed URL (Ed25519 key)
cdn_url = f'https://cdn.example.com/{full}'
expiration = datetime.datetime.utcnow() + \
datetime.timedelta(minutes=15)
signed = sign_cdn_url(
url=cdn_url,
key_name='cdn-signing-key',
key_value=CDN_KEY_BYTES,
expiration=expiration
)
return {'url': signed}Eventarc Pipeline
GCS Event → Eventarc triggerThumbnail gen (Cloud Functions)Virus scan (Cloud Run job)Metadata → FirestoreQuota update → FirestoreAudit → Cloud Logging
Eventarc routes GCS finalize events to Cloud Functions. Dead-letter topic in Pub/Sub for failures. Malware scan quarantines to separate bucket.
Observability
6
Monitoring, Audit & CostCloud Audit Logs
Admin Activity (always on)
Data Access logs (enabled)
GCS object read/write events
Log sink → BigQuery for analysis
Per-tenant access reports
Cloud Monitoring
Alert: cross-tenant access attempt
Alert: Cloud Armor block spike
Alert: storage quota > 80%
CDN cache hit ratio dashboard
Custom metrics per tenant
Security Command Center
SCC Premium: threat detection
Sensitive Data Protection (DLP)
IAM recommender findings
VPC-SC violation alerts
Compliance dashboard (CIS/PCI)
Cost Management
Labels: tenant_id on all resources
Billing export → BigQuery
Per-tenant cost breakdown
Budget alerts per label
Committed Use Discounts
⚔
Tenant Isolation Matrix (GCP)★
Deployment Checklist (GCP)1
Create GCS Bucket
Uniform access, versioning, CMEK default encryption, public access prevention enforced, lifecycle rules (Nearline/Coldline).
2
Set Up Identity Platform
Enable multi-tenancy. Create tenant containers. Add Blocking Function for custom claims. Enable MFA.
3
Deploy Global LB + Cloud CDN
Backend bucket with CDN enabled. Managed SSL cert. Cloud CDN signed URL key (Ed25519). URL map + host rules.
4
Deploy Cloud Functions
Auth middleware: JWT validation + tenant extraction. V4 signed URL generation. Path traversal protection.
5
Configure IAM Conditions
Workforce Identity Pool + attribute mapping. IAM Condition bindings with resource.name prefix matching per tenant.
6
Create Per-Tenant KMS Keys
Key ring per region. Per-tenant CryptoKey with automatic 90-day rotation. IAM binding to tenant service account.
7
Set Up VPC Service Controls
Service perimeter around storage.googleapis.com. Access levels for corp identity. Ingress/egress policies.
8
Attach Cloud Armor
Security policy on LB. OWASP CRS rules, rate limiting per IP, geo-blocking, bot management, adaptive protection.
9
Enable Eventarc Pipeline
GCS finalize trigger → Cloud Functions (scan, thumbnail, index). Pub/Sub DLQ. Firestore metadata + quota update.
10
Enable Monitoring & Audit
Data Access audit logs enabled. Log sink → BigQuery. SCC Premium. Billing export with tenant labels. Alert policies.
GCP Multi-Tenant Blob Storage · Cloud Storage + Cloud CDN · Identity Platform · IAM Conditions · VPC-SC · Production Reference
Ready to Build?
This architecture can be customized for your specific needs. Let's discuss how to implement this pattern for your organization, or explore variations that better match your requirements.
Start a Project