RisiAi Logo

RisiAi Consulting

AI Strategy & Implementation Expert

← 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 S3Cloud Storage (GCS)
CloudFront + OACCloud CDN + Signed URLs
Cognito User PoolIdentity Platform (Firebase Auth)
Lambda@EdgeCloud Functions / Cloud Run
API GatewayCloud Endpoints / Apigee / API Gateway
WAF + ShieldCloud Armor
KMS CMKCloud KMS (CMEK)
DynamoDBFirestore
CloudTrailCloud Audit Logs
CloudWatchCloud Monitoring + Cloud Logging
EventBridge → LambdaEventarc → Cloud Functions
STS Session TagsIAM 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 Layer
CLIENT
Tenant A — Acme Corp
tenant_id: acme-001
📊 Dashboards, reports, media
🔒 /tenants/acme-001/* only
CLIENT
Tenant B — Globex Inc
tenant_id: globex-002
📄 Documents, contracts, uploads
🔒 /tenants/globex-002/* only
CLIENT
Tenant C — Initech
tenant_id: initech-003
🖼️ Images, user content
🔒 /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 Identity
IDP
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 Security
GLB
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 JWT
2
Cloud Function validates + extracts tenant
3
Generates V4 signed URL for tenant prefix
4
CDN serves cached content (signed key)
5
GCS origin fetch if cache miss
CDN 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 Bucket
GCS
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 token
IAM 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 Layer
Cloud 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 & Cost
Cloud 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)
LayerGCP MechanismEnforcementRisk
GCS Prefixtenants/{id}/ partitionIAM Conditions + resource.nameLow
IAM Conditionsattribute.tenant_id matchingWorkforce Identity FederationLow
Cloud KMSPer-tenant CMEK keysKey IAM binding per tenantVery Low
Cloud CDNV4 Signed URLs (Ed25519)Key + expiry + path scopeLow
Cloud FunctionsTenant prefix injectionServer-side validationLow
Cloud ArmorPer-tenant rate limitingSecurity policy rulesMedium
VPC Service ControlsService perimeter on GCSIngress/egress rulesVery Low
Audit LogsData Access + Admin ActivityDetection controlDetective
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