Bluewoo HRMS
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

AspectDetails
ServiceGoogle Cloud Secret Manager
PurposeStore sensitive credentials
AccessCloud Run services via IAM
Cost~$0.03/secret/month + $0.03/10K accesses

Why Secret Manager?

  1. No secrets in code: Environment variables come from Secret Manager
  2. Version control: Track secret changes over time
  3. Access control: IAM-based permissions
  4. Automatic rotation: Can integrate with rotation workflows
  5. Audit logging: Track who accessed what when

HRMS Secrets Inventory

Secret NameServiceDescription
hrms-database-urlAPIPostgreSQL connection string
hrms-jwt-secretAPIJWT signing key
hrms-auth-secretWebAuth.js session encryption
hrms-google-client-idWebGoogle OAuth client ID
hrms-google-client-secretWebGoogle OAuth client secret
hrms-mongodb-uriAIMongoDB Atlas connection string
hrms-openai-api-keyAIOpenAI API key
hrms-redis-urlAPIRedis connection string

Step 1: Enable Secret Manager API

gcloud services enable secretmanager.googleapis.com

Step 2: Create Secrets

Generate Random Secrets

# Generate JWT secret (256-bit)
openssl rand -base64 32

# Generate Auth.js secret
openssl rand -base64 32

Create 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-west1

Step 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"
done

Step 5: Use Secrets in Cloud Run

# 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:latest

Step 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-id

Environment-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-prod

Security 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=50

3. 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-secret

Troubleshooting

Permission Denied

Error: permission denied to access secret "hrms-database-url"

Fix:

  1. Verify service account has secretmanager.secretAccessor role
  2. Check secret exists: gcloud secrets describe hrms-database-url
  3. Verify IAM binding: gcloud secrets get-iam-policy hrms-database-url

Secret Not Found

Error: NOT_FOUND: Secret [hrms-database-url] not found

Fix:

  1. Check secret name spelling
  2. Verify project: gcloud config get-value project
  3. List secrets: gcloud secrets list

Version Not Found

Error: NOT_FOUND: Secret Version [projects/.../secrets/hrms-jwt-secret/versions/5] not found

Fix:

  1. List versions: gcloud secrets versions list hrms-jwt-secret
  2. 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

ItemCost
Active secret versions$0.06/version/month
Access operations$0.03/10,000 accesses
8 secrets, moderate access~$1/month