Skip to main content

GCP Secret Manager

Overview

All Eli Health services are migrating to Google Cloud Secret Manager for centralized secrets management. This eliminates the need for:

  • .env files on local machines
  • Secrets stored in GitHub Actions
  • Manual secret sharing between developers
  • Secrets in Terraform tfvars files

Why Secret Manager?

Before (Old Way)After (GCP Secret Manager)
Secrets in .env filesSecrets in GCP
Copy .env from colleaguegcloud secrets versions access
Secrets in GitHub ActionsCloud Run reads from Secret Manager
Update .env on every machineUpdate once in GCP, applies everywhere
Risk of committing secretsNo secrets on disk
No audit trailFull audit log of who accessed what

Architecture

Service Migration Status

ServiceStatusSecrets LocationNotes
eli-kpi✅ CompleteGCP Secret ManagerAll secrets migrated Jan 2026
eli-hae⏳ PendingTerraform tfvarsNeeds migration
eli-backend⏳ PendingTerraform tfvarsNeeds migration
eli-docs⏳ PendingGitHub ActionsLow priority (no sensitive secrets)
eli-app⏳ Pending.env filesMobile app - different pattern

Secrets Inventory

eli-kpi Secrets (Migrated)

Secret NamePurposeUsed By
anthropic-api-keyAI analysis APIKPI insights
loopwork-api-tokenSubscription dataLoopwork dashboard
ads-data-reader-keyAds analyticsAds dashboard
kpi-google-client-idOAuth loginGoogle authentication
kpi-google-client-secretOAuth loginGoogle authentication
kpi-session-secretCookie encryptionSession management

Common Secrets (To Be Migrated)

Secret NameUsed ByCurrent Location
ANTHROPIC_API_KEYHAE, BackendTerraform
SENDGRID_API_KEYBackendTerraform
STRIPE_API_KEYBackendTerraform
FIREBASE_*MultipleTerraform/GitHub

How It Works

For Cloud Run Services

In the GitHub Actions workflow, secrets are mounted at deploy time:

gcloud run deploy $SERVICE_NAME \
--update-secrets=ANTHROPIC_API_KEY=anthropic-api-key:latest \
--update-secrets=LOOPWORK_API_TOKEN=loopwork-api-token:latest

Cloud Run automatically:

  1. Fetches the secret value from Secret Manager
  2. Injects it as an environment variable
  3. Refreshes on each new deployment

For Local Development

Instead of maintaining .env files, developers fetch secrets directly:

# One-time setup: authenticate with GCP
gcloud auth login
gcloud auth application-default login

# Fetch secrets to a local file
./scripts/fetch-secrets.sh > .env.local
source .env.local

# Or use inline for a single command
ANTHROPIC_API_KEY=$(gcloud secrets versions access latest --secret=anthropic-api-key --project=eli-health-prod) npm run dev

Example fetch-secrets.sh script:

#!/bin/bash
PROJECT="eli-health-prod"

echo "# Secrets from GCP Secret Manager"
echo "# Generated: $(date)"
echo ""
echo "ANTHROPIC_API_KEY=$(gcloud secrets versions access latest --secret=anthropic-api-key --project=$PROJECT)"
echo "LOOPWORK_API_TOKEN=$(gcloud secrets versions access latest --secret=loopwork-api-token --project=$PROJECT)"

Creating New Secrets

1. Create the Secret

# Create a new secret
echo -n "your-secret-value" | gcloud secrets create my-new-secret \
--project=eli-health-prod \
--data-file=-

# Or create empty and add version later
gcloud secrets create my-new-secret --project=eli-health-prod
echo -n "your-secret-value" | gcloud secrets versions add my-new-secret \
--project=eli-health-prod \
--data-file=-

2. Grant Access to Service Account

# Find the service account for your Cloud Run service
# Format: SERVICE-NAME@PROJECT.iam.gserviceaccount.com

gcloud secrets add-iam-policy-binding my-new-secret \
--project=eli-health-prod \
--member="serviceAccount:kpi-service-us@eli-health-prod.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"

3. Use in Cloud Run Deploy

Add to your deploy workflow:

--update-secrets=MY_ENV_VAR=my-new-secret:latest

Rotating Secrets

# Add a new version (becomes "latest" automatically)
echo -n "new-secret-value" | gcloud secrets versions add my-secret \
--project=eli-health-prod \
--data-file=-

# Force Cloud Run to pick up new version
gcloud run services update SERVICE_NAME \
--region=us-east1 \
--project=eli-health-prod \
--update-secrets=MY_ENV_VAR=my-secret:latest

Viewing Secrets

# List all secrets
gcloud secrets list --project=eli-health-prod

# List versions of a secret
gcloud secrets versions list my-secret --project=eli-health-prod

# Access a specific version
gcloud secrets versions access latest --secret=my-secret --project=eli-health-prod
gcloud secrets versions access 2 --secret=my-secret --project=eli-health-prod

Access Control

Secrets are protected by IAM. Only these principals can access secrets:

PrincipalAccess Level
Service accounts (e.g., kpi-service-us@...)Individual secrets they need
Admin users (chip@eli.health, etc.)All secrets
GitHub Actions (via Workload Identity)Deploy-time access only

Developers access secrets through their Google account (gcloud auth login).

Migration Guide

To migrate a service from .env/Terraform to Secret Manager:

Step 1: Identify Current Secrets

# Check what environment variables the service uses
grep -r "process.env" src/ | grep -oP 'process\.env\.\K[A-Z_]+' | sort | uniq

Step 2: Create Secrets in GCP

# For each secret, create in Secret Manager
echo -n "$CURRENT_VALUE" | gcloud secrets create secret-name \
--project=eli-health-prod \
--data-file=-

Step 3: Grant Service Account Access

# Grant access for each secret
gcloud secrets add-iam-policy-binding secret-name \
--project=eli-health-prod \
--member="serviceAccount:SERVICE@eli-health-prod.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"

Step 4: Update Deploy Workflow

Replace --set-env-vars with --update-secrets:

# Before
--set-env-vars="API_KEY=${{ secrets.API_KEY }}"

# After
--update-secrets=API_KEY=api-key-secret:latest

Step 5: Remove Old Secrets

  1. Remove from GitHub Actions secrets
  2. Remove from Terraform tfvars
  3. Update .env.example to reference Secret Manager

Troubleshooting

"Permission denied" when accessing secret

# Check if you're logged in
gcloud auth list

# Re-authenticate if needed
gcloud auth login
gcloud auth application-default login

Cloud Run fails to start with secret error

# Check service account has access
gcloud secrets get-iam-policy my-secret --project=eli-health-prod

# Grant access if missing
gcloud secrets add-iam-policy-binding my-secret \
--project=eli-health-prod \
--member="serviceAccount:YOUR-SERVICE-ACCOUNT" \
--role="roles/secretmanager.secretAccessor"

Secret shows "0 versions"

The secret was created but no value was added:

echo -n "your-value" | gcloud secrets versions add my-secret \
--project=eli-health-prod \
--data-file=-

Best Practices

  1. Never hardcode secrets in code - always use environment variables
  2. Use descriptive names - kpi-google-client-id not google-id
  3. Document in this page when adding new secrets
  4. Rotate regularly - at least quarterly for sensitive secrets
  5. Use separate secrets per environment - don't share prod/dev secrets
  6. Add .env* to .gitignore - prevent accidental commits