################################################################################ # Environment: gcp # Main — SentryAgent.ai AgentIdP on Google Cloud Platform # # Architecture: # Internet → Cloud Run (Google-managed TLS, auto-scaling) → # Cloud SQL PostgreSQL 14 (private IP, REGIONAL HA) + # Memorystore Redis 7 (STANDARD_HA, in-transit encryption) # via Serverless VPC Access connector # # All secrets stored in GCP Secret Manager — Cloud Run reads them at startup. # No sensitive values in state (except where Terraform internals require it). ################################################################################ terraform { required_version = ">= 1.6.0" required_providers { google = { source = "hashicorp/google" version = ">= 5.20.0" } google-beta = { source = "hashicorp/google-beta" version = ">= 5.20.0" } random = { source = "hashicorp/random" version = ">= 3.6.0" } } # Remote state — configure your backend here. # Example using GCS: # # backend "gcs" { # bucket = "sentryagent-terraform-state" # prefix = "agentidp/gcp/production" # } } provider "google" { project = var.project_id region = var.region } provider "google-beta" { project = var.project_id region = var.region } ################################################################################ # Enable required GCP APIs ################################################################################ resource "google_project_service" "apis" { for_each = toset([ "run.googleapis.com", "sqladmin.googleapis.com", "redis.googleapis.com", "vpcaccess.googleapis.com", "secretmanager.googleapis.com", "servicenetworking.googleapis.com", "cloudresourcemanager.googleapis.com", "iam.googleapis.com", ]) project = var.project_id service = each.value disable_on_destroy = false } ################################################################################ # Locals ################################################################################ locals { name_prefix = "${var.project}-${var.environment}" common_labels = { environment = var.environment project = replace(var.project, "-", "_") managed_by = "terraform" } } ################################################################################ # VPC Network ################################################################################ resource "google_compute_network" "main" { name = "${local.name_prefix}-vpc" auto_create_subnetworks = false project = var.project_id depends_on = [google_project_service.apis] } resource "google_compute_subnetwork" "private" { name = "${local.name_prefix}-private-subnet" ip_cidr_range = var.vpc_cidr region = var.region network = google_compute_network.main.id project = var.project_id private_ip_google_access = true log_config { aggregation_interval = "INTERVAL_10_MIN" flow_sampling = 0.5 metadata = "INCLUDE_ALL_METADATA" } } ################################################################################ # Private Services Access — required for Cloud SQL private IP ################################################################################ resource "google_compute_global_address" "private_services" { name = "${local.name_prefix}-private-services-range" purpose = "VPC_PEERING" address_type = "INTERNAL" prefix_length = 20 network = google_compute_network.main.id project = var.project_id } resource "google_service_networking_connection" "private_services" { network = google_compute_network.main.id service = "servicenetworking.googleapis.com" reserved_peering_ranges = [google_compute_global_address.private_services.name] depends_on = [google_project_service.apis] } ################################################################################ # Serverless VPC Access Connector # Cloud Run uses this to reach Cloud SQL (private IP) and Memorystore. ################################################################################ resource "google_vpc_access_connector" "main" { name = "${local.name_prefix}-connector" region = var.region project = var.project_id ip_cidr_range = var.vpc_connector_cidr network = google_compute_network.main.name min_instances = 2 max_instances = 10 machine_type = "e2-micro" depends_on = [google_project_service.apis] } ################################################################################ # Service Account for Cloud Run ################################################################################ resource "google_service_account" "cloud_run" { account_id = "${var.project}-${var.environment}-run-sa" display_name = "AgentIdP Cloud Run Service Account (${var.environment})" project = var.project_id } ################################################################################ # Secret Manager — create secrets and grant the SA access ################################################################################ resource "google_secret_manager_secret" "database_url" { secret_id = "${local.name_prefix}-database-url" project = var.project_id replication { auto {} } labels = local.common_labels depends_on = [google_project_service.apis] } resource "google_secret_manager_secret_version" "database_url" { secret = google_secret_manager_secret.database_url.id # Build the DATABASE_URL from Cloud SQL private IP output. secret_data = "postgresql://${var.db_username}:${var.db_password}@${google_sql_database_instance.main.private_ip_address}:5432/${var.db_name}?sslmode=require" depends_on = [google_sql_database_instance.main] } resource "google_secret_manager_secret" "redis_url" { secret_id = "${local.name_prefix}-redis-url" project = var.project_id replication { auto {} } labels = local.common_labels depends_on = [google_project_service.apis] } resource "google_secret_manager_secret_version" "redis_url" { secret = google_secret_manager_secret.redis_url.id # Memorystore Redis with in-transit encryption uses the rediss:// scheme. secret_data = "rediss://${google_redis_instance.main.host}:${google_redis_instance.main.port}" depends_on = [google_redis_instance.main] } resource "google_secret_manager_secret" "jwt_private_key" { secret_id = "${local.name_prefix}-jwt-private-key" project = var.project_id replication { auto {} } labels = local.common_labels depends_on = [google_project_service.apis] } resource "google_secret_manager_secret_version" "jwt_private_key" { secret = google_secret_manager_secret.jwt_private_key.id secret_data = var.jwt_private_key } resource "google_secret_manager_secret" "jwt_public_key" { secret_id = "${local.name_prefix}-jwt-public-key" project = var.project_id replication { auto {} } labels = local.common_labels depends_on = [google_project_service.apis] } resource "google_secret_manager_secret_version" "jwt_public_key" { secret = google_secret_manager_secret.jwt_public_key.id secret_data = var.jwt_public_key } resource "google_secret_manager_secret" "vault_token" { count = var.vault_token != "" ? 1 : 0 secret_id = "${local.name_prefix}-vault-token" project = var.project_id replication { auto {} } labels = local.common_labels depends_on = [google_project_service.apis] } resource "google_secret_manager_secret_version" "vault_token" { count = var.vault_token != "" ? 1 : 0 secret = google_secret_manager_secret.vault_token[0].id secret_data = var.vault_token } # Grant the Cloud Run SA access to each secret resource "google_secret_manager_secret_iam_member" "run_database_url" { project = var.project_id secret_id = google_secret_manager_secret.database_url.secret_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.cloud_run.email}" } resource "google_secret_manager_secret_iam_member" "run_redis_url" { project = var.project_id secret_id = google_secret_manager_secret.redis_url.secret_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.cloud_run.email}" } resource "google_secret_manager_secret_iam_member" "run_jwt_private_key" { project = var.project_id secret_id = google_secret_manager_secret.jwt_private_key.secret_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.cloud_run.email}" } resource "google_secret_manager_secret_iam_member" "run_jwt_public_key" { project = var.project_id secret_id = google_secret_manager_secret.jwt_public_key.secret_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.cloud_run.email}" } resource "google_secret_manager_secret_iam_member" "run_vault_token" { count = var.vault_token != "" ? 1 : 0 project = var.project_id secret_id = google_secret_manager_secret.vault_token[0].secret_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.cloud_run.email}" } ################################################################################ # Cloud SQL — PostgreSQL 14, private IP, REGIONAL HA ################################################################################ resource "google_sql_database_instance" "main" { name = "${local.name_prefix}-pg14" database_version = "POSTGRES_14" region = var.region project = var.project_id deletion_protection = var.deletion_protection settings { tier = var.db_tier availability_type = var.db_availability_type disk_type = "PD_SSD" disk_size = 50 disk_autoresize = true ip_configuration { ipv4_enabled = false # No public IP private_network = google_compute_network.main.id require_ssl = true } backup_configuration { enabled = true start_time = "03:00" point_in_time_recovery_enabled = true transaction_log_retention_days = 7 backup_retention_settings { retained_backups = 7 retention_unit = "COUNT" } } maintenance_window { day = 7 # Sunday hour = 5 update_track = "stable" } insights_config { query_insights_enabled = true query_string_length = 1024 record_application_tags = true record_client_address = false } database_flags { name = "log_connections" value = "on" } database_flags { name = "log_disconnections" value = "on" } database_flags { name = "log_min_duration_statement" value = "1000" } user_labels = local.common_labels } depends_on = [google_service_networking_connection.private_services] } resource "google_sql_database" "main" { name = var.db_name instance = google_sql_database_instance.main.name project = var.project_id } resource "google_sql_user" "app" { name = var.db_username instance = google_sql_database_instance.main.name password = var.db_password project = var.project_id } ################################################################################ # Memorystore Redis 7 — STANDARD_HA (primary + replica), TLS enabled ################################################################################ resource "google_redis_instance" "main" { name = "${local.name_prefix}-redis" tier = var.memorystore_tier memory_size_gb = var.memorystore_memory_size_gb region = var.region project = var.project_id redis_version = var.memorystore_redis_version # Private connectivity via the VPC authorized_network = google_compute_network.main.id connect_mode = "PRIVATE_SERVICE_ACCESS" # TLS in transit transit_encryption_mode = "SERVER_AUTHENTICATION" # No AUTH token for Memorystore — access is controlled by VPC network policy. # If AUTH is required, set auth_enabled = true and read the generated auth_string output. auth_enabled = true redis_configs = { lazyfree-lazy-eviction = "yes" lazyfree-lazy-expire = "yes" } maintenance_policy { weekly_maintenance_window { day = "SUNDAY" start_time { hours = 6 minutes = 0 seconds = 0 nanos = 0 } } } labels = local.common_labels depends_on = [google_service_networking_connection.private_services] } ################################################################################ # Module: AgentIdP (Cloud Run) ################################################################################ module "agentidp" { source = "../../modules/agentidp" provider_type = "gcp" environment = var.environment project = var.project app_image = "sentryagent/agentidp:${var.app_image_tag}" app_port = 3000 gcp_project_id = var.project_id gcp_region = var.region gcp_service_account_email = google_service_account.cloud_run.email gcp_vpc_connector_name = google_vpc_access_connector.main.id gcp_min_instances = var.cloud_run_min_instances gcp_max_instances = var.cloud_run_max_instances gcp_cpu = var.cloud_run_cpu gcp_memory = var.cloud_run_memory gcp_cors_origin = var.cors_origin gcp_policy_dir = "/app/policies" gcp_vault_addr = var.vault_addr gcp_vault_mount = var.vault_mount gcp_secret_database_url_id = google_secret_manager_secret.database_url.secret_id gcp_secret_redis_url_id = google_secret_manager_secret.redis_url.secret_id gcp_secret_jwt_private_key_id = google_secret_manager_secret.jwt_private_key.secret_id gcp_secret_jwt_public_key_id = google_secret_manager_secret.jwt_public_key.secret_id gcp_secret_vault_token_id = var.vault_token != "" ? google_secret_manager_secret.vault_token[0].secret_id : "" depends_on = [ google_secret_manager_secret_version.database_url, google_secret_manager_secret_version.redis_url, google_secret_manager_secret_version.jwt_private_key, google_secret_manager_secret_version.jwt_public_key, google_secret_manager_secret_iam_member.run_database_url, google_secret_manager_secret_iam_member.run_redis_url, google_secret_manager_secret_iam_member.run_jwt_private_key, google_secret_manager_secret_iam_member.run_jwt_public_key, ] }