300 lines
8.9 KiB
Markdown
300 lines
8.9 KiB
Markdown
|
|
# Google Secret Manager Setup
|
||
|
|
|
||
|
|
This guide explains how to set up and use Google Secret Manager for storing application secrets in production and staging environments.
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The application uses **Google Secret Manager** to securely store sensitive configuration values (API keys, database credentials, etc.) in production and staging environments, while using local `.env` files for development.
|
||
|
|
|
||
|
|
### Environment-based Configuration
|
||
|
|
|
||
|
|
- **Development/Test**: Uses `.env.development` or `.env.test` files (via dotenv)
|
||
|
|
- **Production/Staging**: Uses Google Secret Manager
|
||
|
|
|
||
|
|
## Prerequisites
|
||
|
|
|
||
|
|
1. **Google Cloud Project**: You need a GCP project with Secret Manager API enabled
|
||
|
|
2. **Service Account**: A service account with Secret Manager access
|
||
|
|
3. **Authentication**: Proper credentials configured for your deployment environment
|
||
|
|
|
||
|
|
## Setup Steps
|
||
|
|
|
||
|
|
### 1. Enable Secret Manager API
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Enable the Secret Manager API in your GCP project
|
||
|
|
gcloud services enable secretmanager.googleapis.com --project=YOUR_PROJECT_ID
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. Create Secrets in Google Cloud
|
||
|
|
|
||
|
|
For each required secret, create it in Google Secret Manager:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Example: Create SUPABASE_URL secret
|
||
|
|
echo -n "https://your-project.supabase.co" | \
|
||
|
|
gcloud secrets create SUPABASE_URL \
|
||
|
|
--data-file=- \
|
||
|
|
--project=YOUR_PROJECT_ID
|
||
|
|
|
||
|
|
# Example: Create STRIPE_SECRET_KEY secret
|
||
|
|
echo -n "sk_live_xxxxx" | \
|
||
|
|
gcloud secrets create STRIPE_SECRET_KEY \
|
||
|
|
--data-file=- \
|
||
|
|
--project=YOUR_PROJECT_ID
|
||
|
|
```
|
||
|
|
|
||
|
|
### Required Secrets
|
||
|
|
|
||
|
|
Create the following secrets in Google Secret Manager:
|
||
|
|
|
||
|
|
| Secret Name | Description | Example |
|
||
|
|
|-------------|-------------|---------|
|
||
|
|
| `SUPABASE_URL` | Supabase project URL | `https://xxx.supabase.co` |
|
||
|
|
| `SUPABASE_SERVICE_ROLE_KEY` | Supabase service role key | `eyJxxx...` |
|
||
|
|
| `SUPABASE_CONNECTION_STRING` | PostgreSQL connection string | `postgresql://...` |
|
||
|
|
| `SUPABASE_CA_CERT` | Supabase CA certificate (optional) | `-----BEGIN CERTIFICATE-----...` |
|
||
|
|
| `STREAM_CHAT_API_KEY` | Stream Chat API key | `xxx` |
|
||
|
|
| `STREAM_CHAT_API_SECRET` | Stream Chat API secret | `xxx` |
|
||
|
|
| `STRIPE_SECRET_KEY` | Stripe secret key | `sk_live_xxx` |
|
||
|
|
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook signing secret | `whsec_xxx` |
|
||
|
|
| `EMAIL_USER` | Gmail/email account | `yourapp@gmail.com` |
|
||
|
|
| `EMAIL_CLIENT_ID` | OAuth client ID for email | `xxx.apps.googleusercontent.com` |
|
||
|
|
| `EMAIL_CLIENT_SECRET` | OAuth client secret | `xxx` |
|
||
|
|
| `EMAIL_REFRESH_TOKEN` | OAuth refresh token | `xxx` |
|
||
|
|
| `R2_ACCOUNT_ID` | Cloudflare R2 account ID | `xxx` |
|
||
|
|
| `R2_ACCESS_KEY_ID` | Cloudflare R2 access key | `xxx` |
|
||
|
|
| `R2_SECRET_ACCESS_KEY` | Cloudflare R2 secret key | `xxx` |
|
||
|
|
| `TASKS_SECRET` | Secret for task authentication | `xxx` |
|
||
|
|
|
||
|
|
### 3. Grant Service Account Access
|
||
|
|
|
||
|
|
Your service account needs permission to access secrets:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Grant Secret Manager Secret Accessor role
|
||
|
|
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
|
||
|
|
--member="serviceAccount:YOUR_SERVICE_ACCOUNT@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
|
||
|
|
--role="roles/secretmanager.secretAccessor"
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4. Set Environment Variables
|
||
|
|
|
||
|
|
The application needs these environment variables to work with Google Secret Manager:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Required in production/staging
|
||
|
|
export GCP_PROJECT=your-project-id # or GOOGLE_CLOUD_PROJECT
|
||
|
|
export NODE_ENV=production # or staging
|
||
|
|
|
||
|
|
# Optional: Use non-default port
|
||
|
|
export PORT=8080
|
||
|
|
|
||
|
|
# Optional: Override CORS origins
|
||
|
|
export CORS_ORIGIN=https://app.xtablo.com
|
||
|
|
|
||
|
|
# Optional: Override XTablo URL
|
||
|
|
export XTABLO_URL=https://app.xtablo.com
|
||
|
|
```
|
||
|
|
|
||
|
|
## Authentication
|
||
|
|
|
||
|
|
### Local Development (Testing Secret Manager)
|
||
|
|
|
||
|
|
For local testing of Secret Manager integration:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Authenticate with gcloud
|
||
|
|
gcloud auth application-default login
|
||
|
|
|
||
|
|
# Set project
|
||
|
|
export GCP_PROJECT=your-project-id
|
||
|
|
export NODE_ENV=staging
|
||
|
|
|
||
|
|
# Run your app
|
||
|
|
npm run dev
|
||
|
|
```
|
||
|
|
|
||
|
|
### Google Cloud Run / Cloud Functions
|
||
|
|
|
||
|
|
Authentication is automatic when running on Google Cloud services. Just ensure:
|
||
|
|
|
||
|
|
1. The service account has the `roles/secretmanager.secretAccessor` role
|
||
|
|
2. The `GCP_PROJECT` environment variable is set (or use the project's default)
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
# Example Cloud Run configuration (cloudbuild.yaml)
|
||
|
|
steps:
|
||
|
|
- name: 'gcr.io/cloud-builders/docker'
|
||
|
|
args: ['build', '-t', 'gcr.io/$PROJECT_ID/api:$COMMIT_SHA', '.']
|
||
|
|
- name: 'gcr.io/cloud-builders/docker'
|
||
|
|
args: ['push', 'gcr.io/$PROJECT_ID/api:$COMMIT_SHA']
|
||
|
|
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
|
||
|
|
entrypoint: gcloud
|
||
|
|
args:
|
||
|
|
- 'run'
|
||
|
|
- 'deploy'
|
||
|
|
- 'xtablo-api'
|
||
|
|
- '--image=gcr.io/$PROJECT_ID/api:$COMMIT_SHA'
|
||
|
|
- '--region=us-central1'
|
||
|
|
- '--platform=managed'
|
||
|
|
- '--set-env-vars=NODE_ENV=production,GCP_PROJECT=$PROJECT_ID'
|
||
|
|
- '--service-account=YOUR_SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com'
|
||
|
|
```
|
||
|
|
|
||
|
|
## Usage in Code
|
||
|
|
|
||
|
|
### Accessing Configuration
|
||
|
|
|
||
|
|
The configuration is loaded asynchronously at startup:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// In index.ts (server startup)
|
||
|
|
import { getConfig } from './config.js';
|
||
|
|
|
||
|
|
async function startServer() {
|
||
|
|
const config = await getConfig();
|
||
|
|
// Use config.STRIPE_SECRET_KEY, config.SUPABASE_URL, etc.
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Backwards Compatibility
|
||
|
|
|
||
|
|
For synchronous access (after initialization):
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { config } from './config.js';
|
||
|
|
|
||
|
|
// This works ONLY after getConfig() has been called
|
||
|
|
// (automatically done in index.ts startup)
|
||
|
|
const stripeKey = config.STRIPE_SECRET_KEY;
|
||
|
|
```
|
||
|
|
|
||
|
|
### Manual Secret Loading
|
||
|
|
|
||
|
|
If you need to load additional secrets not in the main config:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { loadSecret } from './secretManager.js';
|
||
|
|
|
||
|
|
// Load a single secret
|
||
|
|
const apiKey = await loadSecret('MY_CUSTOM_SECRET');
|
||
|
|
|
||
|
|
// Load with fallback to environment variable
|
||
|
|
import { loadSecretWithFallback } from './secretManager.js';
|
||
|
|
const token = await loadSecretWithFallback('MY_TOKEN', 'MY_TOKEN_ENV_VAR');
|
||
|
|
```
|
||
|
|
|
||
|
|
## Secret Versioning
|
||
|
|
|
||
|
|
Google Secret Manager supports versioning:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Add a new version of a secret
|
||
|
|
echo -n "new-secret-value" | \
|
||
|
|
gcloud secrets versions add SUPABASE_URL \
|
||
|
|
--data-file=- \
|
||
|
|
--project=YOUR_PROJECT_ID
|
||
|
|
|
||
|
|
# The app automatically uses the "latest" version
|
||
|
|
# You can also specify a specific version in code if needed
|
||
|
|
```
|
||
|
|
|
||
|
|
## Security Best Practices
|
||
|
|
|
||
|
|
1. **Never commit secrets** to version control
|
||
|
|
2. **Use different secrets** for each environment (dev, staging, production)
|
||
|
|
3. **Rotate secrets regularly** using Secret Manager versioning
|
||
|
|
4. **Limit access** to Secret Manager using IAM roles
|
||
|
|
5. **Audit access** using Cloud Audit Logs
|
||
|
|
6. **Use service accounts** with minimal permissions
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### "Missing required environment variable" error
|
||
|
|
|
||
|
|
**Problem**: App fails to start with validation errors
|
||
|
|
|
||
|
|
**Solution**: Ensure all required secrets are created in Secret Manager
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# List all secrets in your project
|
||
|
|
gcloud secrets list --project=YOUR_PROJECT_ID
|
||
|
|
|
||
|
|
# View a specific secret's value
|
||
|
|
gcloud secrets versions access latest --secret=SUPABASE_URL --project=YOUR_PROJECT_ID
|
||
|
|
```
|
||
|
|
|
||
|
|
### "Permission denied" errors
|
||
|
|
|
||
|
|
**Problem**: App can't access secrets
|
||
|
|
|
||
|
|
**Solution**: Check IAM permissions
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Check service account permissions
|
||
|
|
gcloud projects get-iam-policy YOUR_PROJECT_ID \
|
||
|
|
--flatten="bindings[].members" \
|
||
|
|
--filter="bindings.members:serviceAccount:YOUR_SERVICE_ACCOUNT@YOUR_PROJECT_ID.iam.gserviceaccount.com"
|
||
|
|
```
|
||
|
|
|
||
|
|
### "GCP_PROJECT environment variable must be set"
|
||
|
|
|
||
|
|
**Problem**: App doesn't know which GCP project to use
|
||
|
|
|
||
|
|
**Solution**: Set the environment variable
|
||
|
|
|
||
|
|
```bash
|
||
|
|
export GCP_PROJECT=your-project-id
|
||
|
|
# or
|
||
|
|
export GOOGLE_CLOUD_PROJECT=your-project-id
|
||
|
|
```
|
||
|
|
|
||
|
|
### Development mode still uses Secret Manager
|
||
|
|
|
||
|
|
**Problem**: Want to use dotenv in development but it's using Secret Manager
|
||
|
|
|
||
|
|
**Solution**: Ensure `NODE_ENV` is set to `development`
|
||
|
|
|
||
|
|
```bash
|
||
|
|
export NODE_ENV=development
|
||
|
|
npm run dev
|
||
|
|
```
|
||
|
|
|
||
|
|
## Cost Considerations
|
||
|
|
|
||
|
|
Google Secret Manager pricing (as of 2024):
|
||
|
|
|
||
|
|
- **Secret versions**: $0.06 per active secret version per month
|
||
|
|
- **Access operations**: $0.03 per 10,000 accesses
|
||
|
|
|
||
|
|
For typical usage:
|
||
|
|
- ~15 secrets = ~$1/month
|
||
|
|
- Caching reduces access operations (app caches secrets after first load)
|
||
|
|
|
||
|
|
## Migration from Environment Variables
|
||
|
|
|
||
|
|
If migrating from environment variables to Secret Manager:
|
||
|
|
|
||
|
|
1. **Create all secrets** in Google Secret Manager
|
||
|
|
2. **Update deployment configs** to set `NODE_ENV=production` and `GCP_PROJECT`
|
||
|
|
3. **Remove sensitive env vars** from deployment configs
|
||
|
|
4. **Keep non-sensitive configs** as env vars (CORS_ORIGIN, PORT, etc.)
|
||
|
|
5. **Test thoroughly** in staging before production
|
||
|
|
|
||
|
|
## References
|
||
|
|
|
||
|
|
- [Google Secret Manager Documentation](https://cloud.google.com/secret-manager/docs)
|
||
|
|
- [IAM Roles for Secret Manager](https://cloud.google.com/secret-manager/docs/access-control)
|
||
|
|
- [@google-cloud/secret-manager NPM](https://www.npmjs.com/package/@google-cloud/secret-manager)
|
||
|
|
|
||
|
|
## Support
|
||
|
|
|
||
|
|
For issues with Secret Manager integration:
|
||
|
|
|
||
|
|
1. Check application logs for detailed error messages
|
||
|
|
2. Verify IAM permissions in Google Cloud Console
|
||
|
|
3. Ensure all required secrets exist in Secret Manager
|
||
|
|
4. Check that service account authentication is working
|
||
|
|
|