Google Cloud Storage Setup for OpenContracts¶
Last Updated: 2026-01-09
This document provides instructions for configuring Google Cloud Storage (GCS) as the storage backend for OpenContracts.
Note: For the authoritative list of GCS environment variables and their defaults, see
config/settings/base.py(search forSTORAGE_BACKEND == "GCP").
Overview¶
OpenContracts supports three storage backends, configurable via the STORAGE_BACKEND environment variable: 1. LOCAL (default) - Files stored on local disk 2. AWS - Amazon S3 cloud storage 3. GCP - Google Cloud Storage
Prerequisites¶
- Google Cloud Platform account
- GCS bucket created
- Service account with appropriate permissions (for production)
Setup Instructions¶
1. Create GCS Bucket¶
# Using gcloud CLI
gcloud storage buckets create gs://YOUR-BUCKET-NAME \
--location=US \
--default-storage-class=STANDARD \
--uniform-bucket-level-access
2. Configure Service Account¶
For Production (Recommended):¶
-
Create a service account:
gcloud iam service-accounts create opencontracts-storage \ --display-name="OpenContracts Storage Service Account" -
Grant necessary permissions:
# Grant Storage Admin role to the service account gcloud projects add-iam-policy-binding YOUR-PROJECT-ID \ --member="serviceAccount:opencontracts-storage@YOUR-PROJECT-ID.iam.gserviceaccount.com" \ --role="roles/storage.admin" # Or for more granular permissions: gcloud storage buckets add-iam-policy-binding gs://YOUR-BUCKET-NAME \ --member="serviceAccount:opencontracts-storage@YOUR-PROJECT-ID.iam.gserviceaccount.com" \ --role="roles/storage.objectAdmin" -
If using GKE/Cloud Run, attach the service account to your workload:
# For GKE kubectl annotate serviceaccount YOUR-KSA \ iam.gke.io/gcp-service-account=opencontracts-storage@YOUR-PROJECT-ID.iam.gserviceaccount.com # For Cloud Run gcloud run services update YOUR-SERVICE \ --service-account=opencontracts-storage@YOUR-PROJECT-ID.iam.gserviceaccount.com
For Local Development:¶
-
Create and download a service account key:
gcloud iam service-accounts keys create ~/opencontracts-gcs-key.json \ --iam-account=opencontracts-storage@YOUR-PROJECT-ID.iam.gserviceaccount.com -
Set the environment variable:
export GOOGLE_APPLICATION_CREDENTIALS=~/opencontracts-gcs-key.json
3. Configure Environment Variables¶
Update your .env file with the following settings:
# Required: Set storage backend to GCP
STORAGE_BACKEND=GCP
# Required: GCS bucket name
GS_BUCKET_NAME=your-bucket-name
# Optional: Google Cloud project ID
GS_PROJECT_ID=your-project-id
# Authentication (choose one method):
# Method 1: Use default credentials (production with Workload Identity)
# Leave GS_CREDENTIALS empty
# Method 2: Specify path to service account key file
GS_CREDENTIALS=/path/to/service-account-key.json
# Method 3: Use environment variable (set outside of Django)
# GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json
# Security Settings
GS_QUERYSTRING_AUTH=true # Generate signed URLs for private files (default: true)
GS_EXPIRATION_SECONDS=86400 # URL expiration in seconds (default: 86400 = 1 day)
GS_FILE_OVERWRITE=false # Prevent file overwrites (default: false)
GS_DEFAULT_ACL= # Leave empty for private files (default: None)
# Performance Settings
GS_BLOB_CHUNK_SIZE=2621440 # Chunk size for uploads in bytes (default: 2621440 = 2.5MB)
GS_MAX_MEMORY_SIZE=0 # Max memory before disk rollover, 0 = no limit (default: 0)
GS_IS_GZIPPED=false # Enable GZIP compression for static files (default: false)
# Optional: Custom endpoint (for emulators or alternative endpoints)
GS_CUSTOM_ENDPOINT= # Custom GCS endpoint URL (default: None)
# Optional: File location prefix within bucket
GS_LOCATION= # Subdirectory prefix for files (default: "")
# Optional: IAM Sign Blob API (required when not using service account key file)
GS_IAM_SIGN_BLOB=false # Use IAM Sign Blob API for signed URLs (default: false)
GS_SA_EMAIL=opencontracts-storage@YOUR-PROJECT-ID.iam.gserviceaccount.com # Service account email for signing
Security Best Practices¶
1. Service Account Permissions¶
Follow the principle of least privilege. The service account needs:
Minimum Required Permissions: - storage.objects.create - Upload files - storage.objects.delete - Delete files - storage.objects.get - Read files - storage.objects.list - List files - storage.buckets.get - Get bucket metadata
For Signed URLs (if using IAM Sign Blob): - iam.serviceAccounts.signBlob - Sign URLs
2. Bucket Configuration¶
# Enable uniform bucket-level access (recommended)
gcloud storage buckets update gs://YOUR-BUCKET-NAME --uniform-bucket-level-access
# Enable versioning for data protection
gcloud storage buckets update gs://YOUR-BUCKET-NAME --versioning
# Set lifecycle rules to delete old versions
cat > lifecycle.json <<EOF
{
"lifecycle": {
"rule": [
{
"action": {"type": "Delete"},
"condition": {
"age": 30,
"isLive": false
}
}
]
}
}
EOF
gcloud storage buckets update gs://YOUR-BUCKET-NAME --lifecycle-file=lifecycle.json
3. Access Control¶
The application implements different ACLs for different file types:
- Static Files:
publicReadACL for CSS, JS, images - Media Files: Private by default, served via signed URLs
4. Security Headers¶
The custom storage backends automatically add security headers:
X-Content-Type-Options: nosniff- Prevent MIME sniffingX-Frame-Options: DENY- Prevent clickjackingContent-Disposition: attachment- Force download for media files
5. Monitoring¶
Set up logging and monitoring:
# Enable access logs
gcloud storage buckets update gs://YOUR-BUCKET-NAME \
--log-bucket=gs://YOUR-LOG-BUCKET \
--log-object-prefix=access-logs/
# Set up alerts for unusual activity
gcloud monitoring alert create \
--display-name="High GCS Egress" \
--condition="..." \
--notification-channels=...
IAM Policy Example¶
Create a custom IAM role with minimal permissions:
{
"title": "OpenContracts Storage Role",
"description": "Minimal permissions for OpenContracts GCS storage",
"stage": "GA",
"includedPermissions": [
"storage.buckets.get",
"storage.objects.create",
"storage.objects.delete",
"storage.objects.get",
"storage.objects.list",
"storage.objects.update"
]
}
Apply the role:
gcloud iam roles create opencontractsStorage \
--project=YOUR-PROJECT-ID \
--file=role-definition.json
gcloud projects add-iam-policy-binding YOUR-PROJECT-ID \
--member="serviceAccount:opencontracts-storage@YOUR-PROJECT-ID.iam.gserviceaccount.com" \
--role="projects/YOUR-PROJECT-ID/roles/opencontractsStorage"
CORS Configuration (if needed)¶
If serving files directly from GCS to browsers:
[
{
"origin": ["https://yourdomain.com"],
"method": ["GET", "HEAD"],
"responseHeader": ["Content-Type"],
"maxAgeSeconds": 3600
}
]
Apply CORS:
gsutil cors set cors.json gs://YOUR-BUCKET-NAME
Migration from AWS S3¶
To migrate existing files from S3 to GCS:
# Using gsutil
gsutil -m rsync -r s3://YOUR-S3-BUCKET gs://YOUR-GCS-BUCKET
# Or using Storage Transfer Service
gcloud transfer jobs create \
--source-bucket=YOUR-S3-BUCKET \
--destination-bucket=YOUR-GCS-BUCKET
Troubleshooting¶
Common Issues¶
- 403 Forbidden Errors
- Check service account permissions
- Verify bucket IAM policies
-
Ensure CORS is configured if accessing from browser
-
Signed URL Issues
- Enable IAM Sign Blob API:
GS_IAM_SIGN_BLOB=true - Verify service account has
iam.serviceAccounts.signBlobpermission -
Check URL expiration time
-
Authentication Errors
- Verify
GOOGLE_APPLICATION_CREDENTIALSis set correctly - Check service account key file permissions
-
For production, ensure Workload Identity is configured
-
Performance Issues
- Adjust
GS_BLOB_CHUNK_SIZEfor large files - Enable
GS_IS_GZIPPEDfor static files - Use Cloud CDN for frequently accessed content