Trovella Wiki

Networking

Firewall rules, DNS configuration, IAP SSH access, the Cloud SQL Proxy, and secret synchronization.

Firewall Rules

Two firewall rules are defined in infra/modules/compute-vm/main.tf:

HTTP/HTTPS (public)

resource "google_compute_firewall" "allow_http_https" {
  name    = "trovella-${var.environment}-allow-http-https"
  network = "default"

  allow {
    protocol = "tcp"
    ports    = ["80", "443"]
  }

  source_ranges = ["0.0.0.0/0"]
  target_tags   = ["trovella-web"]
}

Allows inbound HTTP and HTTPS from any IP. The VM is tagged trovella-web to match this rule. Port 80 traffic is redirected to 443 by Caddy.

IAP SSH (restricted)

resource "google_compute_firewall" "allow_iap_ssh" {
  name    = "trovella-${var.environment}-allow-iap-ssh"
  network = "default"

  allow {
    protocol = "tcp"
    ports    = ["22"]
  }

  source_ranges = ["35.235.240.0/20"]
  target_tags   = ["iap-ssh"]
}

Allows SSH only from Google's Identity-Aware Proxy IP range (35.235.240.0/20). There is no public port 22. The VM is tagged iap-ssh to match this rule.

What Is Not Open

  • No public SSH (port 22) -- brute-force attacks are impossible
  • No direct database access (port 5432) -- Cloud SQL is accessed via the Auth Proxy sidecar
  • No direct Typesense access (port 8108) -- internal Docker network only
  • No direct Inngest access (port 8288) -- SSH tunnel only
  • No ICMP/ping -- default GCP behavior, no explicit rule added

DNS Configuration

DNS is managed in Cloudflare (registrar transfer from GoDaddy pending ~mid-May 2026).

RecordTypeValueProxy
trovella.aiAVM static IPDNS only (grey cloud)
www.trovella.aiAVM static IPDNS only (grey cloud)

Both records point to the same VM static IP. Caddy handles the www-to-apex redirect.

DNS-only mode is required because Caddy uses HTTP-01 ACME challenges for Let's Encrypt certificate issuance. If Cloudflare proxy mode were enabled, Cloudflare would intercept the challenge requests and certificate issuance would fail.

IAP SSH Access

All administrative access to the VM goes through Identity-Aware Proxy (IAP). IAP authenticates the developer's Google account before allowing the SSH tunnel.

Direct SSH

gcloud compute ssh trovella-prod-vm \
  --zone=us-central1-a --project=trovella-prod \
  --tunnel-through-iap

SSH with Command

gcloud compute ssh trovella-prod-vm \
  --zone=us-central1-a --project=trovella-prod \
  --tunnel-through-iap \
  --command="docker compose -f /opt/trovella/docker-compose.prod.yml ps"

SSH Tunnel for Admin Dashboards

# Inngest dashboard
gcloud compute ssh trovella-prod-vm \
  --zone=us-central1-a --project=trovella-prod \
  --tunnel-through-iap -- -L 8288:localhost:8288

# Drizzle Studio
gcloud compute ssh trovella-prod-vm \
  --zone=us-central1-a --project=trovella-prod \
  --tunnel-through-iap -- -L 4983:localhost:4983

SCP via IAP

The deploy pipeline uses SCP through IAP to transfer files to the VM:

gcloud compute scp \
  infra/docker-compose.prod.yml \
  infra/Caddyfile \
  infra/sync-secrets-vm.sh \
  trovella-prod-vm:~ \
  --zone=us-central1-a --project=trovella-prod \
  --tunnel-through-iap --quiet

IAM Requirements

IAP access requires two IAM roles:

RolePurpose
roles/iap.tunnelResourceAccessorAllows creating IAP tunnels to the VM
roles/compute.instanceAdmin.v1Allows SSH access to Compute Engine instances

Both are granted to the GitHub Actions service account in infra/modules/wif/main.tf for automated deploys, and to the developer's Google account for manual access.

Cloud SQL Proxy

The VM connects to Cloud SQL through the Cloud SQL Auth Proxy, running as a Docker Compose sidecar container:

web container
  |
  | postgresql://cloud-sql-proxy:5432/trovella
  v
cloud-sql-proxy container (Docker network)
  |
  | IAM auth + TLS
  v
Cloud SQL (trovella-prod:us-central1:trovella-prod)

How It Works

  1. The cloud-sql-proxy container authenticates using the VM service account's IAM credentials (via the GCE metadata server)
  2. The VM service account has roles/cloudsql.client, which authorizes proxy connections
  3. The proxy establishes a TLS-encrypted tunnel to Cloud SQL
  4. The web container connects to cloud-sql-proxy:5432 on the Docker network -- no SSL configuration needed in the application
  5. Cloud SQL authorized networks restrict access to the VM's static IP as defense-in-depth

DATABASE_URL Rewriting

The sync-secrets-vm.sh script automatically rewrites DATABASE_URL to route through the proxy:

# The secret stores: postgresql://user:pass@<cloud-sql-public-ip>:5432/trovella
# The script rewrites to: postgresql://user:pass@cloud-sql-proxy:5432/trovella
sed -i -E 's|(DATABASE_URL="postgresql://[^@]+@)[^:/]+:[0-9]+/|\1cloud-sql-proxy:5432/|' "$TEMP_FILE"

This means the DATABASE_URL secret in GCP Secret Manager contains the direct Cloud SQL URL (with public IP), but the application always connects through the proxy.

Secret Synchronization

Application secrets are stored in GCP Secret Manager and synced to the VM at deploy time by infra/sync-secrets-vm.sh.

Flow

  1. CI deploy job SCPs sync-secrets-vm.sh to the VM
  2. The script reads each secret from Secret Manager using gcloud secrets versions access latest
  3. Secrets are written to a temp file, then atomically moved to /opt/trovella/.env
  4. The .env file is chmod 600 (readable only by root)
  5. Docker Compose reads the .env file via the env_file directive

Secrets Mapped

Secret Manager IDEnvironment Variable
trovella-anthropic-api-keyANTHROPIC_API_KEY
trovella-google-ai-api-keyGOOGLE_AI_API_KEY
trovella-database-urlDATABASE_URL
trovella-better-auth-secretBETTER_AUTH_SECRET
trovella-better-auth-urlBETTER_AUTH_URL
trovella-google-oauth-client-idGOOGLE_CLIENT_ID
trovella-google-oauth-client-secretGOOGLE_CLIENT_SECRET
trovella-resend-api-keyRESEND_API_KEY
trovella-upstash-redis-urlREDIS_URL
trovella-upstash-redis-tokenUPSTASH_REDIS_TOKEN
trovella-sentry-dsnSENTRY_DSN
trovella-inngest-event-keyINNGEST_EVENT_KEY
trovella-inngest-signing-keyINNGEST_SIGNING_KEY
trovella-typesense-api-keyTYPESENSE_API_KEY

Static Variables

The script also writes non-secret configuration:

NODE_ENV=production
HOSTNAME=0.0.0.0
PORT=3000
NEXT_PUBLIC_BETTER_AUTH_URL=https://trovella.ai
TYPESENSE_URL=http://typesense:8108
INNGEST_BASE_URL=http://inngest:8288
CLOUD_SQL_CONNECTION_NAME=trovella-prod:us-central1:trovella-prod

Adding a New Secret

  1. Create the secret in Terraform: add to the secrets list in infra/environments/prod/main.tf
  2. Set the secret value: gcloud secrets versions add trovella-<name> --data-file=- --project=trovella-prod
  3. Add the mapping to infra/sync-secrets-vm.sh
  4. Add the mapping to scripts/sync-secrets.sh (local dev)
  5. Add the variable to apps/web/.env.example
  6. If NEXT_PUBLIC_*, also add as a build arg in apps/web/Dockerfile and the build-push CI job

Workload Identity Federation

The CI/CD pipeline authenticates to GCP without long-lived service account keys using Workload Identity Federation (WIF), defined in infra/modules/wif/main.tf.

GitHub Actions exchanges an OIDC token from token.actions.githubusercontent.com for a short-lived GCP access token. The WIF pool is scoped to the specific GitHub repository via an attribute_condition. The service account (github-actions-ci) has cross-project IAM grants for deploying, reading secrets, and accessing IAP tunnels.

For details on the deploy pipeline that uses WIF, see Delivery -- Pipeline.

On this page