Deployment
Secret Manager Setup
Step-by-step guide to using Google Secret Manager with HRMS
Secret Manager Setup
This guide explains how to securely manage application secrets using Google Cloud Secret Manager for the HRMS application.
Overview
| Aspect | Details |
|---|---|
| Service | Google Cloud Secret Manager |
| Purpose | Store sensitive credentials |
| Access | Cloud Run services via IAM |
| Cost | ~$0.03/secret/month + $0.03/10K accesses |
Why Secret Manager?
- No secrets in code: Environment variables come from Secret Manager
- Version control: Track secret changes over time
- Access control: IAM-based permissions
- Automatic rotation: Can integrate with rotation workflows
- Audit logging: Track who accessed what when
HRMS Secrets Inventory
| Secret Name | Service | Description |
|---|---|---|
hrms-database-url | API | PostgreSQL connection string |
hrms-jwt-secret | API | JWT signing key |
hrms-auth-secret | Web | Auth.js session encryption |
hrms-google-client-id | Web | Google OAuth client ID |
hrms-google-client-secret | Web | Google OAuth client secret |
hrms-mongodb-uri | AI | MongoDB Atlas connection string |
hrms-openai-api-key | AI | OpenAI API key |
hrms-redis-url | API | Redis connection string |
Step 1: Enable Secret Manager API
gcloud services enable secretmanager.googleapis.comStep 2: Create Secrets
Generate Random Secrets
# Generate JWT secret (256-bit)
openssl rand -base64 32
# Generate Auth.js secret
openssl rand -base64 32Create Each Secret
# Database URL (from Cloud SQL setup)
echo -n "postgresql://hrms_app:PASSWORD@/hrms_prod?host=/cloudsql/bluewoo-hrms:europe-west1:hrms-db" | \
gcloud secrets create hrms-database-url \
--data-file=- \
--replication-policy="user-managed" \
--locations="europe-west1"
# JWT Secret
echo -n "$(openssl rand -base64 32)" | \
gcloud secrets create hrms-jwt-secret \
--data-file=- \
--replication-policy="user-managed" \
--locations="europe-west1"
# Auth.js Secret
echo -n "$(openssl rand -base64 32)" | \
gcloud secrets create hrms-auth-secret \
--data-file=- \
--replication-policy="user-managed" \
--locations="europe-west1"
# Google OAuth (from Google Cloud Console)
echo -n "YOUR_CLIENT_ID.apps.googleusercontent.com" | \
gcloud secrets create hrms-google-client-id \
--data-file=- \
--replication-policy="user-managed" \
--locations="europe-west1"
echo -n "YOUR_CLIENT_SECRET" | \
gcloud secrets create hrms-google-client-secret \
--data-file=- \
--replication-policy="user-managed" \
--locations="europe-west1"
# MongoDB URI (from Atlas setup)
echo -n "mongodb+srv://hrms_ai_app:PASSWORD@cluster.mongodb.net/hrms_ai" | \
gcloud secrets create hrms-mongodb-uri \
--data-file=- \
--replication-policy="user-managed" \
--locations="europe-west1"
# OpenAI API Key
echo -n "sk-..." | \
gcloud secrets create hrms-openai-api-key \
--data-file=- \
--replication-policy="user-managed" \
--locations="europe-west1"
# Redis URL (from Memorystore setup)
echo -n "redis://10.0.0.1:6379" | \
gcloud secrets create hrms-redis-url \
--data-file=- \
--replication-policy="user-managed" \
--locations="europe-west1"Step 3: Verify Secrets Created
# List all HRMS secrets
gcloud secrets list --filter="name:hrms"
# Output:
# NAME CREATED REPLICATION_POLICY LOCATIONS
# hrms-auth-secret 2024-01-15T10:00:00 user-managed europe-west1
# hrms-database-url 2024-01-15T10:00:00 user-managed europe-west1
# hrms-google-client-id 2024-01-15T10:00:00 user-managed europe-west1
# hrms-google-client-secret 2024-01-15T10:00:00 user-managed europe-west1
# hrms-jwt-secret 2024-01-15T10:00:00 user-managed europe-west1
# hrms-mongodb-uri 2024-01-15T10:00:00 user-managed europe-west1
# hrms-openai-api-key 2024-01-15T10:00:00 user-managed europe-west1
# hrms-redis-url 2024-01-15T10:00:00 user-managed europe-west1Step 4: Grant Access to Service Account
The runtime service account needs permission to read secrets.
export PROJECT_ID="bluewoo-hrms"
export SA_EMAIL="hrms-runtime@${PROJECT_ID}.iam.gserviceaccount.com"
# Grant Secret Accessor role
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/secretmanager.secretAccessor"
# Or grant access to specific secrets only (more secure)
for secret in hrms-database-url hrms-jwt-secret hrms-auth-secret hrms-google-client-id hrms-google-client-secret hrms-mongodb-uri hrms-openai-api-key hrms-redis-url; do
gcloud secrets add-iam-policy-binding $secret \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/secretmanager.secretAccessor"
doneStep 5: Use Secrets in Cloud Run
Method 1: Environment Variables (Recommended)
# Deploy with secrets as environment variables
gcloud run deploy hrms-api \
--image=... \
--region=europe-west1 \
--service-account=hrms-runtime@bluewoo-hrms.iam.gserviceaccount.com \
--set-secrets="DATABASE_URL=hrms-database-url:latest,JWT_SECRET=hrms-jwt-secret:latest,REDIS_URL=hrms-redis-url:latest"Method 2: Mounted Files
# Mount secrets as files (useful for certificates)
gcloud run deploy hrms-api \
--image=... \
--set-secrets="/secrets/db=hrms-database-url:latest"GitHub Actions Deployment
- name: Deploy API
uses: google-github-actions/deploy-cloudrun@v2
with:
service: hrms-api
region: europe-west1
image: ${{ env.IMAGE }}
flags: |
--service-account=hrms-runtime@bluewoo-hrms.iam.gserviceaccount.com
--set-secrets=DATABASE_URL=hrms-database-url:latest,JWT_SECRET=hrms-jwt-secret:latestStep 6: Update Secrets
When you need to rotate or update a secret:
# Create new version
echo -n "new-secret-value" | \
gcloud secrets versions add hrms-jwt-secret --data-file=-
# New version is automatically "latest"
# Cloud Run will use new value on next cold start
# Force Cloud Run to restart (use new secret immediately)
gcloud run services update hrms-api \
--region=europe-west1 \
--update-env-vars="FORCE_RESTART=$(date +%s)"Step 7: View Secret Values (Admin Only)
# View latest version
gcloud secrets versions access latest --secret=hrms-database-url
# View specific version
gcloud secrets versions access 1 --secret=hrms-database-url
# List all versions
gcloud secrets versions list hrms-database-url⚠️ Security: Only access secrets when necessary. Access is logged.
Secret Naming Convention
{project}-{category}-{name}
Examples:
- hrms-database-url
- hrms-jwt-secret
- hrms-google-client-idEnvironment-Specific Secrets
For staging vs production, use different secrets:
# Staging
gcloud secrets create hrms-staging-database-url ...
# Production
gcloud secrets create hrms-prod-database-url ...Or use same secret name with different projects:
# In staging project
gcloud secrets create hrms-database-url --project=bluewoo-hrms-staging
# In production project
gcloud secrets create hrms-database-url --project=bluewoo-hrms-prodSecurity Best Practices
1. Principle of Least Privilege
# Grant access to specific secrets, not all
gcloud secrets add-iam-policy-binding hrms-database-url \
--member="serviceAccount:hrms-runtime@..." \
--role="roles/secretmanager.secretAccessor"2. Enable Audit Logging
# View who accessed secrets
gcloud logging read 'resource.type="secretmanager.googleapis.com/Secret"' \
--limit=503. Use Version Pinning in Production
# Pin to specific version (not "latest")
gcloud run deploy hrms-api \
--set-secrets="DATABASE_URL=hrms-database-url:3"4. Set Expiration Notifications
# Create alert for secret access
gcloud alpha monitoring policies create \
--display-name="Secret Access Alert" \
--condition-filter='resource.type="secretmanager.googleapis.com/Secret"'5. Rotate Secrets Regularly
Create a rotation schedule:
# Example: Rotate JWT secret monthly
# 1. Generate new secret
NEW_SECRET=$(openssl rand -base64 32)
# 2. Add as new version
echo -n "$NEW_SECRET" | gcloud secrets versions add hrms-jwt-secret --data-file=-
# 3. Restart services
gcloud run services update hrms-api --region=europe-west1 --no-traffic
# 4. Verify, then disable old version
gcloud secrets versions disable 1 --secret=hrms-jwt-secretTroubleshooting
Permission Denied
Error: permission denied to access secret "hrms-database-url"Fix:
- Verify service account has
secretmanager.secretAccessorrole - Check secret exists:
gcloud secrets describe hrms-database-url - Verify IAM binding:
gcloud secrets get-iam-policy hrms-database-url
Secret Not Found
Error: NOT_FOUND: Secret [hrms-database-url] not foundFix:
- Check secret name spelling
- Verify project:
gcloud config get-value project - List secrets:
gcloud secrets list
Version Not Found
Error: NOT_FOUND: Secret Version [projects/.../secrets/hrms-jwt-secret/versions/5] not foundFix:
- List versions:
gcloud secrets versions list hrms-jwt-secret - Use valid version number or
latest
Cold Start Delay
Secret loading adds ~100-200ms to cold start.
Mitigation:
- Use minimum instances > 0 for production
- Cache secrets in application memory
Cost Estimation
| Item | Cost |
|---|---|
| Active secret versions | $0.06/version/month |
| Access operations | $0.03/10,000 accesses |
| 8 secrets, moderate access | ~$1/month |