From 09402901f492e53b6b08bab0c28aa7b7a4674d93 Mon Sep 17 00:00:00 2001 From: Benjamin Edwards Date: Wed, 14 May 2025 17:16:39 -0400 Subject: [PATCH 1/3] wip --- gcp/.header.md | 33 ++++ gcp/byo-project/cache.tf | 18 +++ gcp/byo-project/cloud_run.tf | 257 ++++++++++++++++++++++++++++++++ gcp/byo-project/database.tf | 43 ++++++ gcp/byo-project/iam.tf | 50 +++++++ gcp/byo-project/loadbalancer.tf | 65 ++++++++ gcp/byo-project/main.tf | 11 ++ gcp/byo-project/secrets.tf | 33 ++++ gcp/byo-project/storage.tf | 19 +++ gcp/byo-project/variables.tf | 111 ++++++++++++++ gcp/byo-project/vpc.tf | 30 ++++ gcp/main.tf | 57 +++++++ gcp/refactor-plan.md | 150 +++++++++++++++++++ gcp/variables.tf | 35 +++++ gcptest/.terraform.lock.hcl | 102 +++++++++++++ gcptest/main.tf | 7 + 16 files changed, 1021 insertions(+) create mode 100644 gcp/.header.md create mode 100644 gcp/byo-project/cache.tf create mode 100644 gcp/byo-project/cloud_run.tf create mode 100644 gcp/byo-project/database.tf create mode 100644 gcp/byo-project/iam.tf create mode 100644 gcp/byo-project/loadbalancer.tf create mode 100644 gcp/byo-project/main.tf create mode 100644 gcp/byo-project/secrets.tf create mode 100644 gcp/byo-project/storage.tf create mode 100644 gcp/byo-project/variables.tf create mode 100644 gcp/byo-project/vpc.tf create mode 100644 gcp/main.tf create mode 100644 gcp/refactor-plan.md create mode 100644 gcp/variables.tf create mode 100644 gcptest/.terraform.lock.hcl create mode 100644 gcptest/main.tf diff --git a/gcp/.header.md b/gcp/.header.md new file mode 100644 index 0000000..47e52d4 --- /dev/null +++ b/gcp/.header.md @@ -0,0 +1,33 @@ +```mermaid +graph TD + subgraph External + Internet[(Internet)] + Users[Web Console / Host Software] + GitHub[(GitHub - Vulnerability Resources)] + end + + subgraph "Google Cloud Platform (GCP)" + subgraph VPC [VPC] + direction LR + subgraph PublicFacing [Public Zone] + LB[Global Load Balancer] + end + subgraph PrivateZone [Private Zone] + CloudRun[Cloud Run: Fleet App] + CloudSQL[Cloud SQL for MySQLvia Auth Proxy] + Redis[Memorystore for Redis] + NAT[Cloud NAT] + end + + CloudRun --> Redis + CloudRun --> CloudSQL + CloudRun --> NAT + end + end + + Users -- "fleet.yourdomain.com" --> Internet + Internet -- "fleet.yourdomain.com" --> LB + LB --> CloudRun + NAT --> GitHub + +``` \ No newline at end of file diff --git a/gcp/byo-project/cache.tf b/gcp/byo-project/cache.tf new file mode 100644 index 0000000..5e5f769 --- /dev/null +++ b/gcp/byo-project/cache.tf @@ -0,0 +1,18 @@ +module "memstore" { + source = "terraform-google-modules/memorystore/google" + version = "~> 14.0" + + name = var.cache_config.name + redis_version = var.cache_config.engine_version + tier = var.cache_config.tier + memory_size_gb = var.cache_config.memory_size + + project_id = var.project_id + region = var.region + enable_apis = true + transit_encryption_mode = "SERVER_AUTHENTICATION" + authorized_network = module.vpc.network_id + connect_mode = var.cache_config.connect_mode + reserved_ip_range = "" + +} \ No newline at end of file diff --git a/gcp/byo-project/cloud_run.tf b/gcp/byo-project/cloud_run.tf new file mode 100644 index 0000000..55ad801 --- /dev/null +++ b/gcp/byo-project/cloud_run.tf @@ -0,0 +1,257 @@ + +locals { + # --- Shared Container Configuration --- + fleet_image_tag = var.fleet_config.image_tag + fleet_resources_limits = { + cpu = var.fleet_config.fleet_cpu + memory = var.fleet_config.fleet_memory + } + # Common Environment Variables - This list of maps can still be used directly + fleet_common_env_vars = [ + { + name = "FLEET_LICENSE_KEY", + value = var.fleet_config.license_key + }, + { + name = "FLEET_SERVER_FORCE_H2C", + value = "true" + }, + { + name = "FLEET_MYSQL_PROTOCOL", + value = "tcp" + }, + { + name = "FLEET_MYSQL_ADDRESS", + value = "${module.mysql.private_ip_address}:3306" + }, + { + name = "FLEET_MYSQL_USERNAME", + value = var.database_config.database_user + }, + { + name = "FLEET_MYSQL_DATABASE", + value = var.database_config.database_name + }, + { + name = "FLEET_MYSQL_PASSWORD", + value_source = { + secret_key_ref = { + secret = google_secret_manager_secret.database_password.secret_id # Corrected to use the versioned secret with random suffix + version = "latest" + } + } + }, + { + name = "FLEET_SERVER_PRIVATE_KEY", + value_source = { + secret_key_ref = { + secret = google_secret_manager_secret.private_key.secret_id # Corrected to use the versioned secret with random suffix + version = "latest" + } + } + }, + { + name = "FLEET_REDIS_ADDRESS", + value = "${module.memstore.host}:${module.memstore.port}" + }, + { + name = "FLEET_LOGGING_JSON", + value = "true" + }, + { + name = "FLEET_LOGGING_DEBUG", + value = var.fleet_config.debug_logging + }, + { + name = "FLEET_SERVER_TLS", + value = "false" + }, + # S3 Variables + { + name = "FLEET_S3_SOFTWARE_INSTALLERS_BUCKET", + value = google_storage_bucket.software_installers.id + }, + { + name = "FLEET_S3_SOFTWARE_INSTALLERS_ACCESS_KEY_ID", + value = google_storage_hmac_key.key.access_id + }, + { + name = "FLEET_S3_SOFTWARE_INSTALLERS_SECRET_ACCESS_KEY", + value = google_storage_hmac_key.key.secret + }, + { + name = "FLEET_S3_SOFTWARE_INSTALLERS_ENDPOINT_URL", + value = "https://storage.googleapis.com" + }, + { + name = "FLEET_S3_SOFTWARE_INSTALLERS_FORCE_S3_PATH_STYLE", + value = "true" + }, + { + name = "FLEET_S3_SOFTWARE_INSTALLERS_REGION", + value = data.google_client_config.current.region + }, + ] + + # --- Shared VPC Access Configuration Parts --- + # These will be used to construct the block + fleet_vpc_network_id = module.vpc.network_id + # Use the direct construction for the subnet ID key as discussed + fleet_vpc_subnet_id = "fleet-subnet" +} + +# --- Cloud Run Service (Main Webserver) --- +resource "google_cloud_run_v2_service" "fleet_service" { + name = "${var.prefix}-service" + location = var.region + project = var.project_id + deletion_protection = false + + ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" + timeouts { + create = "300s" + update = "300s" + } + + template { + service_account = google_service_account.fleet_run_sa.email # Defined in iam.tf + + # Define vpc_access block directly + vpc_access { + network_interfaces { + network = local.fleet_vpc_network_id + subnetwork = local.fleet_vpc_subnet_id + } + egress = "ALL_TRAFFIC" + } + + containers { + image = local.fleet_image_tag + ports { + name = "h2c" + container_port = 8080 + } + command = ["/bin/sh"] + args = [ + "-c", + "fleet prepare --no-prompt=true db; exec fleet serve" + ] + + startup_probe { + initial_delay_seconds = 10 + timeout_seconds = 5 + period_seconds = 3 + failure_threshold = 3 + tcp_socket { + port = 8080 + } + } + liveness_probe { + http_get { + path = "/healthz" + } + } + + resources { + limits = local.fleet_resources_limits + } + + dynamic "env" { + for_each = local.fleet_common_env_vars + content { + name = env.value.name + value = try(env.value.value, null) + dynamic "value_source" { + for_each = try(env.value.value_source, null) != null ? [env.value.value_source] : [] + content { + secret_key_ref { + secret = value_source.value.secret_key_ref.secret + version = value_source.value.secret_key_ref.version + } + } + } + } + } + } + + scaling { + min_instance_count = var.fleet_config.min_instance_count + max_instance_count = var.fleet_config.max_instance_count + } + } + + depends_on = [ + google_service_account.fleet_run_sa, + google_secret_manager_secret_version.database_password, + google_secret_manager_secret_version.private_key, + ] +} + +# --- Cloud Run Job (Migrations) --- +resource "google_cloud_run_v2_job" "fleet_migration_job" { + + name = "${var.prefix}-migration-job" + location = var.region + project = var.project_id + + template { + template { # Double template for jobs + service_account = google_service_account.fleet_run_sa.email # Defined in iam.tf + + # Define vpc_access block directly + vpc_access { + network_interfaces { + network = local.fleet_vpc_network_id + subnetwork = local.fleet_vpc_subnet_id + } + egress = "ALL_TRAFFIC" + } + + timeout = "3600s" + + containers { + image = local.fleet_image_tag + # Define resources block directly + resources { + limits = local.fleet_resources_limits + } + # Define env block directly, iterating over the local list + dynamic "env" { + for_each = local.fleet_common_env_vars + content { + name = env.value.name + value = try(env.value.value, null) + dynamic "value_source" { + for_each = try(env.value.value_source, null) != null ? [env.value.value_source] : [] + content { + secret_key_ref { + secret = value_source.value.secret_key_ref.secret + version = value_source.value.secret_key_ref.version + } + } + } + } + } + + command = ["fleet"] + args = ["prepare", "db", "--no-prompt=true"] + } + } + } + + depends_on = [ + google_service_account.fleet_run_sa, + google_secret_manager_secret_version.database_password, + ] +} + +resource "google_compute_region_network_endpoint_group" "neg" { + name = "${var.prefix}-neg" + region = var.region + project = var.project_id + network_endpoint_type = "SERVERLESS" # This type works for Cloud Run v2 services + cloud_run { + service = google_cloud_run_v2_service.fleet_service.name # Reference the v2 service name + } + depends_on = [google_cloud_run_v2_service.fleet_service] +} diff --git a/gcp/byo-project/database.tf b/gcp/byo-project/database.tf new file mode 100644 index 0000000..dfa3815 --- /dev/null +++ b/gcp/byo-project/database.tf @@ -0,0 +1,43 @@ +resource "random_id" "suffix" { + byte_length = 5 +} + + +module "private-service-access" { + source = "terraform-google-modules/sql-db/google//modules/private_service_access" + version = "~> 25.0" + + project_id = var.project_id + vpc_network = module.vpc.network_name + deletion_policy = "ABANDON" +} + +module "mysql" { + source = "terraform-google-modules/sql-db/google//modules/mysql" + version = "~> 25.0" + + name = var.database_config.name + project_id = var.project_id + deletion_protection = var.database_config.deletion_protection + database_version = var.database_config.database_version + tier = var.database_config.tier + region = var.region + random_instance_name = true + enable_default_user = true + enable_default_db = true + user_name = var.database_config.database_user + db_name = var.database_config.database_name + db_collation = var.database_config.collation + db_charset = var.database_config.charset + + ip_configuration = { + ipv4_enabled = false + # We never set authorized networks, we need all connections via the + # public IP to be mediated by Cloud SQL. + authorized_networks = [] + require_ssl = false + private_network = module.vpc.network_self_link + } + + module_depends_on = [module.private-service-access.peering_completed] +} \ No newline at end of file diff --git a/gcp/byo-project/iam.tf b/gcp/byo-project/iam.tf new file mode 100644 index 0000000..4a58178 --- /dev/null +++ b/gcp/byo-project/iam.tf @@ -0,0 +1,50 @@ + +# ------------------------------------- +# Service Account for Cloud Run Service & Job +# ------------------------------------- + +resource "google_service_account" "fleet_run_sa" { + project = var.project_id + account_id = "${var.prefix}-run-sa" + display_name = "Service Account for Fleet Cloud Run Service and Jobs" +} + +resource "google_project_iam_member" "fleet_run_sa_sql_instance_user" { + project = var.project_id + role = "roles/cloudsql.instanceUser" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" +} + +# Recommended for Cloud Run standard logging +resource "google_project_iam_member" "fleet_run_sa_log_writer" { + project = var.project_id + role = "roles/logging.logWriter" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" +} + +# Recommended for Cloud Run standard metrics +resource "google_project_iam_member" "fleet_run_sa_monitoring_writer" { + project = var.project_id + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" +} + + +resource "google_secret_manager_secret_iam_member" "fleet_run_sa_db_secret_access" { + project = var.project_id + secret_id = google_secret_manager_secret.database_password.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" + + depends_on = [google_secret_manager_secret.database_password] +} + +resource "google_secret_manager_secret_iam_member" "fleet_run_sa_private_key_secret_access" { + project = var.project_id + secret_id = google_secret_manager_secret.private_key.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" + + depends_on = [google_secret_manager_secret.private_key] +} + diff --git a/gcp/byo-project/loadbalancer.tf b/gcp/byo-project/loadbalancer.tf new file mode 100644 index 0000000..8b25541 --- /dev/null +++ b/gcp/byo-project/loadbalancer.tf @@ -0,0 +1,65 @@ +locals { + # Clean the DNS record name for use in managed SSL cert domains (remove trailing dot) + managed_ssl_domain = trim(var.dns_record_name, ".") +} + +# Create/Manage the DNS Zone in Cloud DNS +resource "google_dns_managed_zone" "fleet_dns_zone" { + project = var.project_id + name = "${var.prefix}-zone" + dns_name = var.dns_zone_name +} + +# Configure the External HTTP(S) Load Balancer +module "fleet_lb" { + source = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs" + version = "~> 12.0" + + project = var.project_id + name = "${var.prefix}-lb" # e.g., fleet-lb + + # SSL Configuration + ssl = true + https_redirect = true # Enforce HTTPS + managed_ssl_certificate_domains = [local.managed_ssl_domain] + + # Backend Configuration + backends = { + default = { + description = "Backend for Fleet Cloud Run service" + enable_cdn = false # Set to true if you want Cloud CDN + protocol = "HTTP" + groups = [ + { + group = google_compute_region_network_endpoint_group.neg.id + } + ] + + log_config = { + enable = true + sample_rate = 1.0 # Log all requests + } + + # IAP (Identity-Aware Proxy) - disabled by default + iap_config = { + enable = false + } + } + } + + depends_on = [google_compute_region_network_endpoint_group.neg] +} + +# Create the DNS A Record for the Load Balancer +resource "google_dns_record_set" "fleet_dns_record" { + project = var.project_id + managed_zone = google_dns_managed_zone.fleet_dns_zone.name + name = var.dns_record_name + type = "A" + ttl = 300 # Time-to-live in seconds + + # Point to the external IP address of the created load balancer + rrdatas = [module.fleet_lb.external_ip] + + depends_on = [module.fleet_lb] +} \ No newline at end of file diff --git a/gcp/byo-project/main.tf b/gcp/byo-project/main.tf new file mode 100644 index 0000000..abe4d03 --- /dev/null +++ b/gcp/byo-project/main.tf @@ -0,0 +1,11 @@ +terraform { + required_version = "~> 1.11" + required_providers { + google = { + source = "hashicorp/google" + version = "6.32.0" + } + } +} + +data "google_client_config" "current" {} diff --git a/gcp/byo-project/secrets.tf b/gcp/byo-project/secrets.tf new file mode 100644 index 0000000..c465e61 --- /dev/null +++ b/gcp/byo-project/secrets.tf @@ -0,0 +1,33 @@ +resource "random_pet" "suffix" { + length = 1 +} + +resource "random_password" "private_key" { + length = 32 +} + +resource "google_secret_manager_secret" "database_password" { + project = var.project_id + secret_id = "fleet-db-password-${random_pet.suffix.id}" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "database_password" { + secret = google_secret_manager_secret.database_password.name + secret_data = module.mysql.generated_user_password +} + +resource "google_secret_manager_secret" "private_key" { + project = var.project_id + secret_id = "fleet-private-key-${random_pet.suffix.id}" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "private_key" { + secret = google_secret_manager_secret.private_key.name + secret_data = random_password.private_key.result +} \ No newline at end of file diff --git a/gcp/byo-project/storage.tf b/gcp/byo-project/storage.tf new file mode 100644 index 0000000..37ab6b2 --- /dev/null +++ b/gcp/byo-project/storage.tf @@ -0,0 +1,19 @@ +resource "google_storage_hmac_key" "key" { + project = var.project_id + service_account_email = google_service_account.fleet_run_sa.email +} + +resource "google_storage_bucket" "software_installers" { + project = var.project_id + name = var.fleet_config.installers_bucket_name + location = var.location + force_destroy = true + + uniform_bucket_level_access = true +} + +resource "google_storage_bucket_iam_member" "hmac_sa_storage_admin" { + bucket = google_storage_bucket.software_installers.name + role = "roles/storage.objectAdmin" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" +} \ No newline at end of file diff --git a/gcp/byo-project/variables.tf b/gcp/byo-project/variables.tf new file mode 100644 index 0000000..c46710f --- /dev/null +++ b/gcp/byo-project/variables.tf @@ -0,0 +1,111 @@ +variable "project_id" { + description = "GCP project ID" +} + +variable "location" { + default = "us" +} + +variable "region" { + default = "us-central1" +} + +variable "prefix" { + default = "fleet" +} + +variable "dns_zone_name" { + description = "The DNS name of the managed zone (e.g., 'my-fleet-infra.com.')" + type = string +} + +variable "dns_record_name" { + description = "The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.')" + type = string +} + +variable "cache_config" { + type = object({ + name = string + tier = string + engine_version = string + connect_mode = string + memory_size = number + }) + default = { + name = "fleet-cache" + tier = "STANDARD_HA" + engine_version = null // defaults to version 7 + connect_mode = "PRIVATE_SERVICE_ACCESS" + memory_size = 1 + } +} + +variable "database_config" { + type = object({ + name = string + database_name = string + database_user = string + collation = string + charset = string + deletion_protection = bool + database_version = string + tier = string + }) + default = { + name = "fleet-mysql" + database_name = "fleet" + database_user = "fleet" + collation = "utf8mb4_unicode_ci" + charset = "utf8mb4" + deletion_protection = false + database_version = "MYSQL_8_0" + tier = "db-n1-standard-1" + } +} + +variable "vpc_config" { + type = object({ + network_name = string + subnets = list(object({ + subnet_name = string + subnet_ip = string + subnet_region = string + subnet_private_access = bool + })) + }) + + default = { + network_name = "fleet-network" + subnets = [ + { + subnet_name = "fleet-subnet" + subnet_ip = "10.10.10.0/24" + subnet_region = "us-central1" + subnet_private_access = true + } + ] + } + +} +variable "fleet_config" { + type = object({ + installers_bucket_name = string + image_tag = string + fleet_cpu = string + fleet_memory = string + debug_logging = bool + license_key = optional(string) + min_instance_count = number + max_instance_count = number + }) + default = { + image_tag = "fleetdm/fleet:v4.67.3" + installers_bucket_name = "fleet-installers-gcp-test-1" // todo fix + fleet_cpu = "1000m" + fleet_memory = "4096Mi" + debug_logging = false + min_instance_count = 1 + max_instance_count = 5 + } +} diff --git a/gcp/byo-project/vpc.tf b/gcp/byo-project/vpc.tf new file mode 100644 index 0000000..5e7a5e4 --- /dev/null +++ b/gcp/byo-project/vpc.tf @@ -0,0 +1,30 @@ +# Example from your vpc.tf (ensure this part is correct) + + +locals { + network_name = "${var.prefix}-network" + subnet_name = "${var.prefix}-subnet" +} + + +module "vpc" { + source = "terraform-google-modules/network/google" + version = "11.0.0" + + project_id = var.project_id + network_name = var.vpc_config.network_name + subnets = var.vpc_config.subnets +} + +module "cloud_router" { + source = "terraform-google-modules/cloud-router/google" + version = "7.0" + name = "${var.prefix}-cloud-router" + project = var.project_id + network = module.vpc.network_name + region = var.region + + nats = [{ + name = "${var.prefix}-vpc-nat" + }] +} diff --git a/gcp/main.tf b/gcp/main.tf new file mode 100644 index 0000000..52d1b12 --- /dev/null +++ b/gcp/main.tf @@ -0,0 +1,57 @@ +terraform { + required_version = "~> 1.11" + required_providers { + google = { + source = "hashicorp/google" + version = "6.32.0" + } + } +} + +provider "google" { + # Credentials used here need Org/Folder level permissions + default_labels = var.labels +} + + +module "project_factory" { + source = "terraform-google-modules/project-factory/google" + version = "~> 18.0.0" + + name = var.project_name + random_project_id = var.random_project_id + org_id = var.org_id + billing_account = var.billing_account_id + + default_service_account = "delete" + + # Enable baseline APIs needed by most projects + your app stack + activate_apis = [ + "compute.googleapis.com", + "sqladmin.googleapis.com", + "redis.googleapis.com", + "run.googleapis.com", + "vpcaccess.googleapis.com", + "secretmanager.googleapis.com", + "storage.googleapis.com", + "dns.googleapis.com", + "iam.googleapis.com", + "cloudresourcemanager.googleapis.com", + "serviceusage.googleapis.com", + "servicenetworking.googleapis.com", + "logging.googleapis.com", + "monitoring.googleapis.com", + "memorystore.googleapis.com", + "serviceconsumermanagement.googleapis.com", + "networkconnectivity.googleapis.com" + ] + + labels = var.labels +} + +module "fleet" { + source = "./byo-project" + project_id = module.project_factory.project_id + dns_record_name = var.dns_record_name + dns_zone_name = var.dns_zone_name +} \ No newline at end of file diff --git a/gcp/refactor-plan.md b/gcp/refactor-plan.md new file mode 100644 index 0000000..7f483b9 --- /dev/null +++ b/gcp/refactor-plan.md @@ -0,0 +1,150 @@ +**Analysis of the Current Terraform Configuration** + +1. **Overall Structure:** The Terraform code is reasonably well-structured, breaking resources down into logical files (`vpc.tf`, `mysql.tf`, `redis.tf`, `cloud_run.tf`, `loadbalancer.tf`, `storage.tf`, etc.). This makes it maintainable. + +2. **Core Components:** + * **Web Server (Fleet):** Deployed using Cloud Run (`google_cloud_run_service`). This aligns well with the serverless goal. It uses environment variables extensively for configuration, including credentials fetched directly from Secret Manager (`value_from`), which is a good practice. It connects to the database via the Cloud SQL connection name annotation and uses a Serverless VPC Access connector for private network access (Redis, Private IP SQL). + * **Redis Cache:** Deployed using Memorystore for Redis (`google_redis_instance`) with private service access. Good choice for managed Redis. + * **Database (MySQL):** Deployed using Cloud SQL (`module "fleet-mysql"`), configured for private IP access. Uses a Terraform module for abstraction. + * **Networking:** A custom VPC, subnet, Serverless VPC Access Connector, Private Service Access for managed services, and Cloud NAT are set up using Terraform modules. This provides the necessary private connectivity. + * **Load Balancing:** An External HTTPS Load Balancer (`module "lb-http"`) is configured using a Serverless Network Endpoint Group (NEG) pointing to the Cloud Run service. It handles SSL termination with managed certificates and DNS integration. + * **Secrets Management:** Google Secret Manager is used to store the database password and a Fleet private key, accessed securely by Cloud Run. + * **Storage:** A GCS bucket is created for "software installers", and an HMAC key is generated for S3-compatible access, passed as environment variables to Cloud Run. + +3. **Serverless & Minimal Configuration Focus:** + * **Successes:** Cloud Run, Memorystore, Cloud SQL (with private IP), Serverless NEG, Managed SSL Certificates, Secret Manager integration all fit the serverless/managed/minimal config goal well. + * **Areas for Review:** The setup still requires explicit VPC, Subnet, VPC Connector, PSA, and NAT configuration. While necessary for private connectivity with this architecture, newer features might simplify parts of this. + +4. **Potential Issues & Outdated Practices (relative to today):** + * **Provider/Module Versions:** `hashicorp/google` v4.51.0, `terraform-google-modules/network` v4.1.0, `GoogleCloudPlatform/lb-http` v6.2.0, `GoogleCloudPlatform/sql-db` v9.0.0, `terraform-google-modules/cloud-router` v6.0 are all significantly outdated. Newer versions offer bug fixes, performance improvements, new features, and support for the latest GCP APIs. + * **Cloud Run Service Account:** The Cloud Run service runs as the *default compute service account* (`data.google_compute_default_service_account.default.email`). This is generally discouraged as this account often has broad permissions. A dedicated service account with least-privilege permissions is recommended. + * **Fleet Image Version:** The Fleet image (`fleetdm/fleet:v4.66.0`) is hardcoded and old. This prevents easy updates and runs outdated software. + * **`google-beta` Provider:** Its necessity should be re-evaluated after updating the main `google` provider. Often, features graduate from beta. + * **HMAC Key Usage:** While functional and required if the application *only* speaks S3 API, if Fleet *could* use native GCS authentication (via Application Default Credentials), using the Cloud Run service account directly would be simpler and avoid managing HMAC keys. However, the env vars (`FLEET_S3_*`) strongly suggest S3 compatibility is expected, making HMAC necessary. + * **Random Pet for Secret Names:** Functional, but less predictable than using `${var.prefix}` or similar structures. Might make finding secrets harder outside of Terraform state. + +**Refactoring Suggestions** + +Here are suggestions to modernize the stack, leveraging newer GCP features and Terraform practices, while maintaining the minimal configuration goal: + +1. **Update Dependencies:** + * **Terraform:** Ensure you are running a recent Terraform version (e.g., 1.5.x or later). + * **Providers:** Update the `hashicorp/google` provider to the latest 5.x version. Check the Terraform Registry for the latest. Remove the `google-beta` provider unless explicitly required by a resource/feature *after* updating the main provider. + * **Modules:** Update all `terraform-google-modules/*` and `GoogleCloudPlatform/*` modules to their latest stable versions. Check their respective repositories/Terraform Registry for release notes and potential breaking changes. Newer module versions often have better integration and more features. + * Example `main.tf`: + ```terraform + terraform { + required_version = "~> 1.5" # Or later + required_providers { + google = { + source = "hashicorp/google" + version = "~> 5.10" # Check latest + } + # google-beta likely not needed anymore + } + } + + provider "google" { + project = var.project_id + region = var.region + } + ``` + * Update `version` constraints in `mysql.tf`, `vpc.tf`, `loadbalancer.tf` etc. + +2. **Implement Dedicated Service Account for Cloud Run:** + * Create a new service account specifically for the Fleet Cloud Run service. + * Grant this SA the necessary roles (e.g., `roles/secretmanager.secretAccessor`, `roles/cloudsql.client`, potentially roles for logging/monitoring if needed). + * Update the Secret Manager IAM bindings (`google_secret_manager_secret_iam_member`) to grant access to this *new* SA instead of the default compute SA. + * Modify the `google_cloud_run_service` definition to use this SA. + * Example additions/modifications: + ```terraform + # In a suitable file like iam.tf or cloud_run.tf + resource "google_service_account" "fleet_run_sa" { + account_id = "${var.prefix}-fleet-run-sa" + display_name = "Service Account for Fleet Cloud Run" + } + + resource "google_project_iam_member" "fleet_run_sa_sql_client" { + project = var.project_id + role = "roles/cloudsql.client" + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" + } + + # Modify secret IAM bindings + resource "google_secret_manager_secret_iam_member" "secret-access" { + secret_id = google_secret_manager_secret.secret.id + role = "roles/secretmanager.secretAccessor" + # Use the new SA email + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" + depends_on = [google_secret_manager_secret.secret] + } + + resource "google_secret_manager_secret_iam_member" "private-key-access" { + secret_id = google_secret_manager_secret.private_key.id + role = "roles/secretmanager.secretAccessor" + # Use the new SA email + member = "serviceAccount:${google_service_account.fleet_run_sa.email}" + depends_on = [google_secret_manager_secret.private_key] + } + + # Modify cloud_run.tf + resource "google_cloud_run_service" "default" { + # ... existing config ... + template { + spec { + # Add this line + service_account_name = google_service_account.fleet_run_sa.email + containers { + # ... existing container config ... + } + } + # ... existing metadata ... + } + # ... rest of config ... + } + ``` + +3. **Parameterize Fleet Image Version:** + * Avoid hardcoding the image version in `variables.tf`. Allow it to be overridden or default to a known recent tag or even `latest` (with caution for production). + * Example `variables.tf`: + ```terraform + variable "image" { + # Update default to a recent stable version + default = "fleetdm/fleet:stable" # Or specific e.g., fleetdm/fleet:v4.XX.Y + description = "Docker image for the Fleet backend service." + } + ``` + +4. **Simplify VPC and Connectivity (Leverage Module Updates):** + * After updating the `terraform-google-modules/network/google` module, check if features like Serverless VPC Access Connector creation, Private Service Access enablement, and Cloud NAT can be configured more directly within the main VPC module definition, potentially reducing the need for separate modules or simplifying their configuration. Consult the module's documentation for the latest usage patterns. + +5. **Update Database/Redis Versions (Optional):** + * Check Cloud SQL and Memorystore documentation for the latest supported versions of MySQL (e.g., `MYSQL_8_0_36` if specific minor versions are beneficial) and Redis. Update `var.db_version` if appropriate and compatible with Fleet. + +6. **Refine Secret Naming (Optional):** + * Consider replacing `random_pet` with a more predictable naming scheme if desired for easier identification in the GCP console. + * Example `cloud_run.tf`: + ```terraform + resource "random_id" "secret_suffix" { + byte_length = 4 + } + + resource "google_secret_manager_secret" "secret" { + # Use prefix and random_id for predictable but unique names + secret_id = "${var.prefix}-fleet-db-password-${random_id.secret_suffix.hex}" + # ... rest ... + } + + resource "google_secret_manager_secret" "private_key" { + secret_id = "${var.prefix}-fleet-private-key-${random_id.secret_suffix.hex}" + # ... rest ... + } + ``` + +7. **Consider Cloud Run V2 Resource (Optional Evaluation):** + * While the current `google_cloud_run_service` likely uses the V2 API backend, review the explicit `google_cloud_run_v2_service` resource introduced in later provider versions. It might offer a cleaner syntax or expose newer features not available via annotations (though most common features *are* available via annotations). For this specific setup, sticking with the updated `google_cloud_run_service` is likely sufficient unless a V2-specific feature is needed. + +8. **Review Load Balancer Module Configuration:** + * Update the `GoogleCloudPlatform/lb-http/google` module and review its input variables. Newer versions might offer simplified configuration for common patterns, better security defaults (e.g., default security policies), or improved logging/monitoring integration. + +By implementing these changes, particularly updating dependencies and using a dedicated service account, you'll significantly modernize the Terraform configuration, improve security, and make it easier to manage updates while preserving the core serverless/managed architecture. diff --git a/gcp/variables.tf b/gcp/variables.tf new file mode 100644 index 0000000..e228736 --- /dev/null +++ b/gcp/variables.tf @@ -0,0 +1,35 @@ +variable "project_name" { + default = "fleet" +} + +variable "org_id" { + description = "organization id" +} + +variable "billing_account_id" { + description = "billing account id" +} + +variable "labels" { + description = "resource labels" + default = {application = "fleet"} + type = map(string) +} + +variable "fleet_image" { + default = "v4.67.3" +} + +variable "dns_zone_name" { + description = "The DNS name of the managed zone (e.g., 'my-fleet-infra.com.')" + type = string +} + +variable "dns_record_name" { + description = "The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.')" + type = string +} + +variable "random_project_id" { + default = true +} \ No newline at end of file diff --git a/gcptest/.terraform.lock.hcl b/gcptest/.terraform.lock.hcl new file mode 100644 index 0000000..c43b1f4 --- /dev/null +++ b/gcptest/.terraform.lock.hcl @@ -0,0 +1,102 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "6.32.0" + constraints = ">= 3.33.0, >= 3.43.0, >= 3.83.0, >= 4.25.0, >= 4.28.0, >= 4.51.0, >= 4.64.0, >= 4.74.0, >= 5.38.0, >= 5.41.0, >= 6.1.0, >= 6.19.0, 6.32.0, < 7.0.0" + hashes = [ + "h1:oAGdA7OWiM1F46yt4g6SoXmPKle5cmPCIMRpm7MaM6U=", + "zh:1e8146395e4fc462b9d85150c253042844207a8491e9fa5751894b6d7a5d1b26", + "zh:2e8f0eeedd4e156ba0958078cd9cf33bad7a60529b44af57dc401b7b082753d2", + "zh:439e1667e2499bfa2235f572c85dcae2f5cf0002ce859371f64cdaec9664c171", + "zh:45b14d6f2e701d8c614d96cf52e26c33e47c7248ca86e9d3bf8425db527b3a84", + "zh:59fc8edcf17a8f45aa6eeef1370a0e1c914758dede5bbb51f783553c878e3811", + "zh:614f7cf5e443fa8f575e739b3ff37c3d986f0b1eb91f4c63579008e5ab3fcc23", + "zh:72956d2425883ce84d010a6f3fb64f74ba4528bd0b3f4990d49c4a37098633f5", + "zh:7f8218e8ca484749b907f5cfedfc9ce605d2eb5fa4088c948bd365e4d014001f", + "zh:8e576976de335e5c6a87feefe285a54881784fd60a664fefc2e9d1d007c38b2a", + "zh:b12f7873063ab121b6330a18158f47601b088d518cb5e6bc7be05f9dedf62326", + "zh:f21580639abb248e47838bb0bed325409cf89688b99629066f611f0085387b80", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/google-beta" { + version = "6.35.0" + constraints = ">= 3.43.0, >= 4.11.0, >= 4.64.0, >= 5.38.0, >= 5.41.0, >= 6.1.0, >= 6.19.0, < 7.0.0" + hashes = [ + "h1:Q1VXolwlo9TM5IatjsII9d5oiwxEb3tzpGUDJQiX9OE=", + "zh:119b4d11dbaff065dc9e91d11d0bfde3076ee21313a61eeee459928ae20ca366", + "zh:137fda0ed8d2d4588021c8f18e61ee0d5219d4f5d95821ee65a1cda3d3574d2c", + "zh:280a5ea88053511443c1b6b2270c415b46df6a7b2ab4e87889d64668c0840440", + "zh:65bd3715520885330cb55289e62baf5354f75de039137657c3b1cf1a5f9761d4", + "zh:7099ca906f54888131da1ceb0a25444b4a9e5a533341cba283d874ef144c51ac", + "zh:b3b1f093f177cf5166b0e38257e8bc0e039cdf27f28693004304c3de387df165", + "zh:b52eefd0a02a87ce1ff5e87026adfe7c693c7e6fa8bfad0e35f70b556486e103", + "zh:bbec0ab75da4d3a972fe2f0bc28a0be8916a8b167d7d889ff6f4611b4536b4a9", + "zh:c7638b10aecfe1659c9c829de52c6b83b58368c1d55c49ebc8945ef289d8f2cf", + "zh:c92260e7b06f5394f101471d0c19759af620e38d145fa54b104d6ed3a68bf437", + "zh:f4bd6cd39368d7a49ffedbbe03b49750316285cd48987a945d3bdc9a4dfb8cce", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/null" { + version = "3.2.4" + constraints = ">= 2.1.0, ~> 3.1" + hashes = [ + "h1:hkf5w5B6q8e2A42ND2CjAvgvSN3puAosDmOJb3zCVQM=", + "zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43", + "zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a", + "zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991", + "zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f", + "zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e", + "zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615", + "zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442", + "zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5", + "zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f", + "zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + constraints = ">= 2.2.0, ~> 3.1" + hashes = [ + "h1:356j/3XnXEKr9nyicLUufzoF4Yr6hRy481KIxRVpK0c=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.13.1" + constraints = ">= 0.5.0" + hashes = [ + "h1:+W+DMrVoVnoXo3f3M4W+OpZbkCrUn6PnqDF33D2Cuf0=", + "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", + "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", + "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", + "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", + "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", + "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", + "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", + "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", + "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", + "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", + ] +} diff --git a/gcptest/main.tf b/gcptest/main.tf new file mode 100644 index 0000000..412b0ce --- /dev/null +++ b/gcptest/main.tf @@ -0,0 +1,7 @@ +module "fleet" { + source = "../gcp" + billing_account_id = "018189-3756B2-E586C1" + org_id = "885948355217" + dns_record_name = "dogfoodgcp.fleetdm.com." + dns_zone_name = "dogfoodgcp.fleetdm.com." +} \ No newline at end of file From dd4bf617feccd9ca0cdf047942c055a8c0dbbae1 Mon Sep 17 00:00:00 2001 From: Benjamin Edwards Date: Tue, 20 May 2025 01:19:50 -0400 Subject: [PATCH 2/3] gcp updates and refactor --- gcp/.header.md | 194 +++++++++++++++++++++- gcp/.terraform-docs.yml | 1 + gcp/README.md | 284 ++++++++++++++++++++++++++++++++ gcp/byo-project/cache.tf | 4 +- gcp/byo-project/cloud_run.tf | 306 ++++++++++++++++------------------- gcp/byo-project/main.tf | 9 +- gcp/byo-project/outputs.tf | 89 ++++++++++ gcp/byo-project/variables.tf | 37 +++-- gcp/main.tf | 8 +- gcp/outputs.tf | 89 ++++++++++ gcp/readme.md | 247 ++++++++++++++++++++++++++++ gcp/refactor-plan.md | 150 ----------------- gcp/variables.tf | 119 +++++++++++++- gcptest/.terraform.lock.hcl | 102 ------------ gcptest/main.tf | 7 - 15 files changed, 1195 insertions(+), 451 deletions(-) create mode 100644 gcp/.terraform-docs.yml create mode 100644 gcp/README.md create mode 100644 gcp/byo-project/outputs.tf create mode 100644 gcp/outputs.tf create mode 100644 gcp/readme.md delete mode 100644 gcp/refactor-plan.md delete mode 100644 gcptest/.terraform.lock.hcl delete mode 100644 gcptest/main.tf diff --git a/gcp/.header.md b/gcp/.header.md index 47e52d4..861be1d 100644 --- a/gcp/.header.md +++ b/gcp/.header.md @@ -1,8 +1,198 @@ +# Terraform Google Cloud FleetDM Deployment + +This Terraform project automates the deployment of Fleet Device Management (FleetDM) on the Google Cloud Platform (GCP). It provisions a new GCP project (or uses an existing one if configured) and deploys all necessary infrastructure components, including: + +* A new Google Cloud Project (via `terraform-google-modules/project-factory`) +* VPC Network and Subnets +* Cloud NAT for outbound internet access +* Cloud SQL for MySQL (database for Fleet) +* Memorystore for Redis (caching for Fleet) +* Google Cloud Storage (GCS) bucket (for Fleet software installers, S3-compatible access configured) +* Google Secret Manager (for storing sensitive data like database passwords and Fleet server private key) +* Cloud Run v2 Service (for the main Fleet application) +* Cloud Run v2 Job (for database migrations) +* External HTTP(S) Load Balancer with managed SSL certificate +* Cloud DNS for managing the Fleet endpoint +* Appropriate IAM service accounts and permissions + +## Prerequisites + +1. **Terraform:** Version `~> 1.11` (as specified in `main.tf`). Install from [terraform.io](https://www.terraform.io/downloads.html). +2. **Google Cloud SDK (`gcloud`):** Installed and configured. Install from [cloud.google.com/sdk](https://cloud.google.com/sdk/docs/install). +3. **GCP Account & Permissions:** + * A Google Cloud Organization ID. + * A Billing Account ID. + * Credentials configured for Terraform to interact with GCP. This typically means running `gcloud auth application-default login` with a user account that has: + * `Organization Administrator` or `Folder Creator` & `Project Creator` roles at the Organization/Folder level (for the `project-factory` module). + * `Billing Account User` role on the Billing Account. + * Once the project is created, subsequent applies for resources within the project will generally require `Owner` or `Editor` on the provisioned project. + * Alternatively, use a Service Account with appropriate permissions. +4. **Registered Domain Name:** You will need a domain name that you can manage DNS records for. This project will create a Cloud DNS Managed Zone for this domain (or a subdomain). +5. **(Optional) FleetDM License Key:** If you have a premium FleetDM license, you can provide it. + +## Configuration + +1. **Clone the repository (if applicable) or ensure you have all the files.** +2. **Create a `terraform.tfvars` file:** + Copy `terraform.tfvars.example` (you'll need to create this, see example below) to `terraform.tfvars` and populate it with your specific values. + + **Example `terraform.tfvars.example`:** + ```hcl + # Required: GCP Organization and Billing + org_id = "YOUR_ORGANIZATION_ID" # e.g., "123456789012" + billing_account_id = "YOUR_BILLING_ACCOUNT_ID" # e.g., "012345-6789AB-CDEF01" + + # Required: DNS Configuration + # The public DNS zone name (e.g., "example.com." - note the trailing dot) + # This project will create/manage this zone in Cloud DNS. + dns_zone_name = "your-domain.com." + # The fully qualified domain name for your Fleet instance (e.g., "fleet.your-domain.com.") + dns_record_name = "fleet.your-domain.com." + + # Optional: Project Configuration + project_name = "my-fleet-project" # Name for the new GCP project + random_project_id = true # Set to false if you want to use project_name as project_id (must be globally unique) + + # Optional: Labels + labels = { + application = "fleetdm" + environment = "production" + owner = "devops-team" + } + + # --- byo-project module specific variables (passed through from root) --- + # You can override defaults from byo-project/variables.tf here if needed, + # or set values for variables that don't have defaults. + + # Example: FleetDM Configuration including migration execution + fleet_config = { + # image_tag = "fleetdm/fleet:v4.x.y" # Specify your desired Fleet version + # license_key = "YOUR_FLEET_LICENSE_KEY" + # installers_bucket_name = "my-unique-fleet-installers" # Must be globally unique + # fleet_cpu = "1000m" + # fleet_memory = "4096Mi" + # debug_logging = false + # min_instance_count = 1 + # max_instance_count = 3 + # exec_migration = true # Set to true to run migrations on `terraform apply` + # (e.g., when changing image_tag) + } + + ``` + + +**Key Variables to Set:** + +* `org_id`: Your GCP Organization ID. +* `billing_account_id`: Your GCP Billing Account ID. +* `dns_zone_name`: The DNS zone that will be created/managed in Cloud DNS (e.g., `mydomain.com.`). **Must end with a dot.** +* `dns_record_name`: The specific DNS record for Fleet (e.g., `fleet.mydomain.com.`). **Must end with a dot.** +* `project_name`: A descriptive name for the project to be created. +* `fleet_config.image_tag`: The Docker image tag for the FleetDM version you want to deploy (e.g., `fleetdm/fleet:v4.67.3`). +* `fleet_config.exec_migration`: Set to `true` when you are upgrading the `fleet_config.image_tag` to automatically run database migrations. Set to `false` if you want to manage migrations manually or if it's not an image upgrade. +* `fleet_config.license_key` (Optional, inside the `fleet_config` object): Your FleetDM license key if you have one. + +Review `variables.tf` and `byo-project/variables.tf` for all available options and their defaults. + + +## Deployment Steps + +1. **Authenticate with GCP:** + Ensure your `gcloud` CLI is authenticated with the account having the necessary permissions. + ```bash + gcloud auth application-default login + ``` + +2. **Initialize Terraform:** + Navigate to the root directory of the project (where `main.tf` is located) and run: + ```bash + terraform init + ``` + +3. **Plan the Deployment:** + Review the changes Terraform will make: + ```bash + terraform plan -out=tfplan + ``` + +4. **Apply the Configuration:** + Provision the resources: + ```bash + terraform apply tfplan + ``` + This process can take a significant amount of time (10–20 minutes or more), especially for the initial project creation, API enablement, and Cloud SQL instance provisioning. + +5. **Update DNS (if not using Cloud DNS for the parent zone):** + If `dns_zone_name` (e.g., `fleet.your-domain.com.`) is a new zone created by this Terraform, and your parent domain (e.g., `your-domain.com.`) is managed elsewhere (e.g., GoDaddy, Cloudflare), you need to delegate this new zone. + * Get the Name Servers (NS) records for the newly created `google_dns_managed_zone` (`${var.prefix}-zone`): + ```bash + # First, get the project ID and zone name if you don't know them + PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1) + DNS_ZONE_INTERNAL_NAME="fleet-zone" # Or as configured by 'prefix' variable + + gcloud dns managed-zones describe ${DNS_ZONE_INTERNAL_NAME} \ + --project=${PROJECT_ID} \ + --format="value(nameServers)" + ``` + * Add these NS records to your parent domain's DNS settings at your domain registrar or DNS provider. + + If `dns_zone_name` (e.g., `your-domain.com.`) was an *existing* zone you own and manage via Cloud DNS in this project, Terraform would add records to it directly. The current setup *creates* the zone, so delegation is key if the parent is external. + +## Accessing FleetDM + +Once the deployment is complete, database migrations have run successfully, and DNS has propagated (which can take a few minutes): + +* Open your web browser and navigate to the `dns_record_name` you configured (e.g., `https://fleet.your-domain.com`). +* You should be greeted by the FleetDM setup page or login screen. + +## Key Resources Created (within the `byo-project` module) + +* **VPC Network (`fleet-network`):** Custom network for Fleet resources. +* **Subnet (`fleet-subnet`):** Subnetwork within the VPC. +* **Cloud Router & NAT (`fleet-cloud-router`, `fleet-vpc-nat`):** For outbound internet access from Cloud Run and other private resources. +* **Cloud SQL for MySQL (`fleet-mysql-xxxx`):** Managed MySQL database. +* **Memorystore for Redis (`fleet-cache`):** Managed Redis instance. +* **GCS Bucket (`fleet-installers-xxxx`):** For S3-compatible storage. +* **Secret Manager Secrets:** + * `fleet-db-password-xxxx`: Stores the generated MySQL user password. + * `fleet-private-key-xxxx`: Stores a generated private key for Fleet server. +* **Service Account (`fleet-run-sa`):** Used by Cloud Run services/jobs with necessary permissions. +* **Cloud Run Service (`fleet-service`):** Hosts the main Fleet application. +* **Cloud Run Job (`fleet-migration-job`):** For database migrations. +* **Network Endpoint Group (`fleet-neg`):** Connects the Load Balancer to the Cloud Run service. +* **Cloud DNS Managed Zone (`fleet-zone`):** Manages DNS records for `dns_zone_name`. +* **External HTTP(S) Load Balancer (`fleet-lb`):** Provides public access to Fleet via HTTPS. +* **DNS A Record:** Points `dns_record_name` to the Load Balancer's IP. + +(Note: `xxxx` indicates a random suffix added by some modules/resources for uniqueness.) + +## Cleaning Up + +To destroy all resources created by this Terraform project: +1. Navigate to the root directory of the project. +2. Run the destroy command: + ```bash + terraform destroy + ``` +3. Confirm by typing `yes` when prompted. + +## Important Considerations + +* **Permissions:** The user or service account running Terraform needs extensive permissions, especially for project creation. For ongoing management within the project, a role like `Project Editor` or more granular roles might suffice. +* **FleetDM Configuration:** This Terraform setup provisions the infrastructure. Further FleetDM application configuration (e.g., SSO, SMTP, agent options) will need to be done through the Fleet UI or API after deployment. +* **Security:** + * Review IAM permissions granted. + * The Load Balancer is configured for HTTPS and redirects HTTP. + * Cloud SQL and Memorystore are not publicly accessible and are connected via Private Service Access. +* **Scalability:** Cloud Run scaling parameters (`min_instance_count`, `max_instance_count`) and database/cache tier can be adjusted in `variables.tf` (via `fleet_config`, `database_config`, `cache_config`) to suit your load. + + +### Networking Diagram ```mermaid graph TD subgraph External Internet[(Internet)] - Users[Web Console / Host Software] + Users[Web Console / fleetd agent] GitHub[(GitHub - Vulnerability Resources)] end @@ -14,7 +204,7 @@ graph TD end subgraph PrivateZone [Private Zone] CloudRun[Cloud Run: Fleet App] - CloudSQL[Cloud SQL for MySQLvia Auth Proxy] + CloudSQL[Cloud SQL for MySQL] Redis[Memorystore for Redis] NAT[Cloud NAT] end diff --git a/gcp/.terraform-docs.yml b/gcp/.terraform-docs.yml new file mode 100644 index 0000000..1d139dd --- /dev/null +++ b/gcp/.terraform-docs.yml @@ -0,0 +1 @@ +header-from: .header.md diff --git a/gcp/README.md b/gcp/README.md new file mode 100644 index 0000000..42f4694 --- /dev/null +++ b/gcp/README.md @@ -0,0 +1,284 @@ +# Terraform Google Cloud FleetDM Deployment + +This Terraform project automates the deployment of Fleet Device Management (FleetDM) on the Google Cloud Platform (GCP). It provisions a new GCP project (or uses an existing one if configured) and deploys all necessary infrastructure components, including: + +* A new Google Cloud Project (via `terraform-google-modules/project-factory`) +* VPC Network and Subnets +* Cloud NAT for outbound internet access +* Cloud SQL for MySQL (database for Fleet) +* Memorystore for Redis (caching for Fleet) +* Google Cloud Storage (GCS) bucket (for Fleet software installers, S3-compatible access configured) +* Google Secret Manager (for storing sensitive data like database passwords and Fleet server private key) +* Cloud Run v2 Service (for the main Fleet application) +* Cloud Run v2 Job (for database migrations) +* External HTTP(S) Load Balancer with managed SSL certificate +* Cloud DNS for managing the Fleet endpoint +* Appropriate IAM service accounts and permissions + +## Prerequisites + +1. **Terraform:** Version `~> 1.11` (as specified in `main.tf`). Install from [terraform.io](https://www.terraform.io/downloads.html). +2. **Google Cloud SDK (`gcloud`):** Installed and configured. Install from [cloud.google.com/sdk](https://cloud.google.com/sdk/docs/install). +3. **GCP Account & Permissions:** + * A Google Cloud Organization ID. + * A Billing Account ID. + * Credentials configured for Terraform to interact with GCP. This typically means running `gcloud auth application-default login` with a user account that has: + * `Organization Administrator` or `Folder Creator` & `Project Creator` roles at the Organization/Folder level (for the `project-factory` module). + * `Billing Account User` role on the Billing Account. + * Once the project is created, subsequent applies for resources within the project will generally require `Owner` or `Editor` on the provisioned project. + * Alternatively, use a Service Account with appropriate permissions. +4. **Registered Domain Name:** You will need a domain name that you can manage DNS records for. This project will create a Cloud DNS Managed Zone for this domain (or a subdomain). +5. **(Optional) FleetDM License Key:** If you have a premium FleetDM license, you can provide it. + +## Configuration + +1. **Clone the repository (if applicable) or ensure you have all the files.** +2. **Create a `terraform.tfvars` file:** + Copy `terraform.tfvars.example` (you'll need to create this, see example below) to `terraform.tfvars` and populate it with your specific values. + + **Example `terraform.tfvars.example`:** + ```hcl + # Required: GCP Organization and Billing + org_id = "YOUR_ORGANIZATION_ID" # e.g., "123456789012" + billing_account_id = "YOUR_BILLING_ACCOUNT_ID" # e.g., "012345-6789AB-CDEF01" + + # Required: DNS Configuration + # The public DNS zone name (e.g., "example.com." - note the trailing dot) + # This project will create/manage this zone in Cloud DNS. + dns_zone_name = "your-domain.com." + # The fully qualified domain name for your Fleet instance (e.g., "fleet.your-domain.com.") + dns_record_name = "fleet.your-domain.com." + + # Optional: Project Configuration + project_name = "my-fleet-project" # Name for the new GCP project + random_project_id = true # Set to false if you want to use project_name as project_id (must be globally unique) + + # Optional: Labels + labels = { + application = "fleetdm" + environment = "production" + owner = "devops-team" + } + + # --- byo-project module specific variables (passed through from root) --- + # You can override defaults from byo-project/variables.tf here if needed, + # or set values for variables that don't have defaults. + + # Example: FleetDM Configuration including migration execution + fleet_config = { + # image_tag = "fleetdm/fleet:v4.x.y" # Specify your desired Fleet version + # license_key = "YOUR_FLEET_LICENSE_KEY" + # installers_bucket_name = "my-unique-fleet-installers" # Must be globally unique + # fleet_cpu = "1000m" + # fleet_memory = "4096Mi" + # debug_logging = false + # min_instance_count = 1 + # max_instance_count = 3 + # exec_migration = true # Set to true to run migrations on `terraform apply` + # (e.g., when changing image_tag) + } + + ``` + +**Key Variables to Set:** +* `org_id`: Your GCP Organization ID. +* `billing_account_id`: Your GCP Billing Account ID. +* `dns_zone_name`: The DNS zone that will be created/managed in Cloud DNS (e.g., `mydomain.com.`). **Must end with a dot.** +* `dns_record_name`: The specific DNS record for Fleet (e.g., `fleet.mydomain.com.`). **Must end with a dot.** +* `project_name`: A descriptive name for the project to be created. +* `fleet_config.image_tag`: The Docker image tag for the FleetDM version you want to deploy (e.g., `fleetdm/fleet:v4.67.3`). +* `fleet_config.exec_migration`: Set to `true` when you are upgrading the `fleet_config.image_tag` to automatically run database migrations. Set to `false` if you want to manage migrations manually or if it's not an image upgrade. +* `fleet_config.license_key` (Optional, inside the `fleet_config` object): Your FleetDM license key if you have one. + +Review `variables.tf` and `byo-project/variables.tf` for all available options and their defaults. + +## Deployment Steps + +1. **Authenticate with GCP:** + Ensure your `gcloud` CLI is authenticated with the account having the necessary permissions. + ```bash + gcloud auth application-default login + ``` + +2. **Initialize Terraform:** + Navigate to the root directory of the project (where `main.tf` is located) and run: + ```bash + terraform init + ``` + +3. **Plan the Deployment:** + Review the changes Terraform will make: + ```bash + terraform plan -out=tfplan + ``` + +4. **Apply the Configuration:** + Provision the resources: + ```bash + terraform apply tfplan + ``` + This process can take a significant amount of time (10–20 minutes or more), especially for the initial project creation, API enablement, and Cloud SQL instance provisioning. + +5. **Update DNS (if not using Cloud DNS for the parent zone):** + If `dns_zone_name` (e.g., `fleet.your-domain.com.`) is a new zone created by this Terraform, and your parent domain (e.g., `your-domain.com.`) is managed elsewhere (e.g., GoDaddy, Cloudflare), you need to delegate this new zone. + * Get the Name Servers (NS) records for the newly created `google_dns_managed_zone` (`${var.prefix}-zone`): + ```bash + # First, get the project ID and zone name if you don't know them + PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1) + DNS_ZONE_INTERNAL_NAME="fleet-zone" # Or as configured by 'prefix' variable + + gcloud dns managed-zones describe ${DNS_ZONE_INTERNAL_NAME} \ + --project=${PROJECT_ID} \ + --format="value(nameServers)" + ``` + * Add these NS records to your parent domain's DNS settings at your domain registrar or DNS provider. + + If `dns_zone_name` (e.g., `your-domain.com.`) was an *existing* zone you own and manage via Cloud DNS in this project, Terraform would add records to it directly. The current setup *creates* the zone, so delegation is key if the parent is external. + +## Accessing FleetDM + +Once the deployment is complete, database migrations have run successfully, and DNS has propagated (which can take a few minutes): + +* Open your web browser and navigate to the `dns_record_name` you configured (e.g., `https://fleet.your-domain.com`). +* You should be greeted by the FleetDM setup page or login screen. + +## Key Resources Created (within the `byo-project` module) + +* **VPC Network (`fleet-network`):** Custom network for Fleet resources. +* **Subnet (`fleet-subnet`):** Subnetwork within the VPC. +* **Cloud Router & NAT (`fleet-cloud-router`, `fleet-vpc-nat`):** For outbound internet access from Cloud Run and other private resources. +* **Cloud SQL for MySQL (`fleet-mysql-xxxx`):** Managed MySQL database. +* **Memorystore for Redis (`fleet-cache`):** Managed Redis instance. +* **GCS Bucket (`fleet-installers-xxxx`):** For S3-compatible storage. +* **Secret Manager Secrets:** + * `fleet-db-password-xxxx`: Stores the generated MySQL user password. + * `fleet-private-key-xxxx`: Stores a generated private key for Fleet server. +* **Service Account (`fleet-run-sa`):** Used by Cloud Run services/jobs with necessary permissions. +* **Cloud Run Service (`fleet-service`):** Hosts the main Fleet application. +* **Cloud Run Job (`fleet-migration-job`):** For database migrations. +* **Network Endpoint Group (`fleet-neg`):** Connects the Load Balancer to the Cloud Run service. +* **Cloud DNS Managed Zone (`fleet-zone`):** Manages DNS records for `dns_zone_name`. +* **External HTTP(S) Load Balancer (`fleet-lb`):** Provides public access to Fleet via HTTPS. +* **DNS A Record:** Points `dns_record_name` to the Load Balancer's IP. + +(Note: `xxxx` indicates a random suffix added by some modules/resources for uniqueness.) + +## Cleaning Up + +To destroy all resources created by this Terraform project: +1. Navigate to the root directory of the project. +2. Run the destroy command: + ```bash + terraform destroy + ``` +3. Confirm by typing `yes` when prompted. + +## Important Considerations + +* **Permissions:** The user or service account running Terraform needs extensive permissions, especially for project creation. For ongoing management within the project, a role like `Project Editor` or more granular roles might suffice. +* **FleetDM Configuration:** This Terraform setup provisions the infrastructure. Further FleetDM application configuration (e.g., SSO, SMTP, agent options) will need to be done through the Fleet UI or API after deployment. +* **Security:** + * Review IAM permissions granted. + * The Load Balancer is configured for HTTPS and redirects HTTP. + * Cloud SQL and Memorystore are not publicly accessible and are connected via Private Service Access. +* **Scalability:** Cloud Run scaling parameters (`min_instance_count`, `max_instance_count`) and database/cache tier can be adjusted in `variables.tf` (via `fleet_config`, `database_config`, `cache_config`) to suit your load. + +### Networking Diagram +```mermaid +graph TD + subgraph External + Internet[(Internet)] + Users[Web Console / fleetd agent] + GitHub[(GitHub - Vulnerability Resources)] + end + + subgraph "Google Cloud Platform (GCP)" + subgraph VPC [VPC] + direction LR + subgraph PublicFacing [Public Zone] + LB[Global Load Balancer] + end + subgraph PrivateZone [Private Zone] + CloudRun[Cloud Run: Fleet App] + CloudSQL[Cloud SQL for MySQL] + Redis[Memorystore for Redis] + NAT[Cloud NAT] + end + + CloudRun --> Redis + CloudRun --> CloudSQL + CloudRun --> NAT + end + end + + Users -- "fleet.yourdomain.com" --> Internet + Internet -- "fleet.yourdomain.com" --> LB + LB --> CloudRun + NAT --> GitHub + +``` + +## Requirements + +| Name | Version | +|---------------------------------------------------------------------------|---------| +| [terraform](#requirement\_terraform) | ~> 1.11 | +| [google](#requirement\_google) | 6.35.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|-------------------------------------------------------------------------------------|-------------------------------------------------|-----------| +| [fleet](#module\_fleet) | ./byo-project | n/a | +| [project\_factory](#module\_project\_factory) | terraform-google-modules/project-factory/google | ~> 18.0.0 | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|----------------------------------------------------------------------------------------------|-------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------:| +| [billing\_account\_id](#input\_billing\_account\_id) | billing account id | `any` | n/a | yes | +| [cache\_config](#input\_cache\_config) | Configuration for the Memorystore (Redis) instance. |
object({
name = string
tier = string
engine_version = string
connect_mode = string
memory_size = number
})
|
{
"connect_mode": "PRIVATE_SERVICE_ACCESS",
"engine_version": null,
"memory_size": 1,
"name": "fleet-cache",
"tier": "STANDARD_HA"
}
| no | +| [database\_config](#input\_database\_config) | Configuration for the Cloud SQL (MySQL) instance. |
object({
name = string
database_name = string
database_user = string
collation = string
charset = string
deletion_protection = bool
database_version = string
tier = string
})
|
{
"charset": "utf8mb4",
"collation": "utf8mb4_unicode_ci",
"database_name": "fleet",
"database_user": "fleet",
"database_version": "MYSQL_8_0",
"deletion_protection": false,
"name": "fleet-mysql",
"tier": "db-n1-standard-1"
}
| no | +| [dns\_record\_name](#input\_dns\_record\_name) | The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.') | `string` | n/a | yes | +| [dns\_zone\_name](#input\_dns\_zone\_name) | The DNS name of the managed zone (e.g., 'my-fleet-infra.com.') | `string` | n/a | yes | +| [fleet\_config](#input\_fleet\_config) | Configuration for the Fleet application deployment. |
object({
installers_bucket_name = string
image_tag = string
fleet_cpu = string
fleet_memory = string
debug_logging = bool
license_key = optional(string)
min_instance_count = number
max_instance_count = number
exec_migration = bool
extra_env_vars = optional(map(string))
extra_secret_env_vars = optional(map(object({
secret = string
version = string
})))
})
|
{
"debug_logging": false,
"exec_migration": true,
"extra_env_vars": {},
"extra_secret_env_vars": {},
"fleet_cpu": "1000m",
"fleet_memory": "4096Mi",
"image_tag": "fleetdm/fleet:v4.67.3",
"installers_bucket_name": "fleet-installers-gcp-test-1",
"max_instance_count": 5,
"min_instance_count": 1
}
| no | +| [fleet\_image](#input\_fleet\_image) | n/a | `string` | `"v4.67.3"` | no | +| [labels](#input\_labels) | resource labels | `map(string)` |
{
"application": "fleet"
}
| no | +| [location](#input\_location) | The general location for resources, e.g., 'us' for GCS buckets. | `string` | `"us"` | no | +| [org\_id](#input\_org\_id) | organization id | `any` | n/a | yes | +| [prefix](#input\_prefix) | A prefix used for naming resources within the byo-project module. | `string` | `"fleet"` | no | +| [project\_name](#input\_project\_name) | n/a | `string` | `"fleet"` | no | +| [random\_project\_id](#input\_random\_project\_id) | n/a | `bool` | `true` | no | +| [region](#input\_region) | The GCP region for regional resources. | `string` | `"us-central1"` | no | +| [vpc\_config](#input\_vpc\_config) | Configuration for the VPC network and subnets. |
object({
network_name = string
subnets = list(object({
subnet_name = string
subnet_ip = string
subnet_region = string
subnet_private_access = bool
}))
})
|
{
"network_name": "fleet-network",
"subnets": [
{
"subnet_ip": "10.10.10.0/24",
"subnet_name": "fleet-subnet",
"subnet_private_access": true,
"subnet_region": "us-central1"
}
]
}
| no | + +## Outputs + +| Name | Description | +|-----------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| +| [cloud\_router\_name](#output\_cloud\_router\_name) | The name of the Cloud Router created for NAT. | +| [cloud\_run\_service\_location](#output\_cloud\_run\_service\_location) | The location of the deployed Fleet Cloud Run service. | +| [cloud\_run\_service\_name](#output\_cloud\_run\_service\_name) | The name of the deployed Fleet Cloud Run service. | +| [dns\_managed\_zone\_name](#output\_dns\_managed\_zone\_name) | The name of the Cloud DNS managed zone created for Fleet. | +| [dns\_managed\_zone\_name\_servers](#output\_dns\_managed\_zone\_name\_servers) | The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these. | +| [fleet\_application\_url](#output\_fleet\_application\_url) | The primary URL to access the Fleet application (via the Load Balancer). | +| [fleet\_service\_account\_email](#output\_fleet\_service\_account\_email) | The email address of the service account used by the Fleet Cloud Run service. | +| [load\_balancer\_ip\_address](#output\_load\_balancer\_ip\_address) | The external IP address of the HTTP(S) Load Balancer. | +| [mysql\_instance\_connection\_name](#output\_mysql\_instance\_connection\_name) | The connection name for the Cloud SQL instance (used by Cloud SQL Proxy). | +| [mysql\_instance\_name](#output\_mysql\_instance\_name) | The name of the Cloud SQL for MySQL instance. | +| [redis\_host](#output\_redis\_host) | The host IP address of the Memorystore for Redis instance. | +| [redis\_instance\_name](#output\_redis\_instance\_name) | The name of the Memorystore for Redis instance. | +| [redis\_port](#output\_redis\_port) | The port number of the Memorystore for Redis instance. | +| [software\_installers\_bucket\_name](#output\_software\_installers\_bucket\_name) | The name of the GCS bucket for Fleet software installers. | +| [software\_installers\_bucket\_url](#output\_software\_installers\_bucket\_url) | The gsutil URL of the GCS bucket for Fleet software installers. | +| [vpc\_network\_name](#output\_vpc\_network\_name) | The name of the VPC network created. | +| [vpc\_network\_self\_link](#output\_vpc\_network\_self\_link) | The self-link of the VPC network created. | +| [vpc\_subnets\_names](#output\_vpc\_subnets\_names) | List of subnet names created in the VPC. | diff --git a/gcp/byo-project/cache.tf b/gcp/byo-project/cache.tf index 5e5f769..e2248bc 100644 --- a/gcp/byo-project/cache.tf +++ b/gcp/byo-project/cache.tf @@ -10,9 +10,9 @@ module "memstore" { project_id = var.project_id region = var.region enable_apis = true - transit_encryption_mode = "SERVER_AUTHENTICATION" + transit_encryption_mode = "DISABLED" authorized_network = module.vpc.network_id connect_mode = var.cache_config.connect_mode - reserved_ip_range = "" + depends_on = [module.private-service-access.peering_completed] } \ No newline at end of file diff --git a/gcp/byo-project/cloud_run.tf b/gcp/byo-project/cloud_run.tf index 55ad801..cf115ab 100644 --- a/gcp/byo-project/cloud_run.tf +++ b/gcp/byo-project/cloud_run.tf @@ -6,191 +6,120 @@ locals { cpu = var.fleet_config.fleet_cpu memory = var.fleet_config.fleet_memory } - # Common Environment Variables - This list of maps can still be used directly - fleet_common_env_vars = [ - { - name = "FLEET_LICENSE_KEY", - value = var.fleet_config.license_key - }, - { - name = "FLEET_SERVER_FORCE_H2C", - value = "true" - }, - { - name = "FLEET_MYSQL_PROTOCOL", - value = "tcp" - }, - { - name = "FLEET_MYSQL_ADDRESS", - value = "${module.mysql.private_ip_address}:3306" - }, - { - name = "FLEET_MYSQL_USERNAME", - value = var.database_config.database_user - }, - { - name = "FLEET_MYSQL_DATABASE", - value = var.database_config.database_name - }, - { - name = "FLEET_MYSQL_PASSWORD", - value_source = { - secret_key_ref = { - secret = google_secret_manager_secret.database_password.secret_id # Corrected to use the versioned secret with random suffix - version = "latest" - } - } - }, - { - name = "FLEET_SERVER_PRIVATE_KEY", - value_source = { - secret_key_ref = { - secret = google_secret_manager_secret.private_key.secret_id # Corrected to use the versioned secret with random suffix - version = "latest" - } - } - }, - { - name = "FLEET_REDIS_ADDRESS", - value = "${module.memstore.host}:${module.memstore.port}" - }, - { - name = "FLEET_LOGGING_JSON", - value = "true" - }, - { - name = "FLEET_LOGGING_DEBUG", - value = var.fleet_config.debug_logging - }, - { - name = "FLEET_SERVER_TLS", - value = "false" - }, - # S3 Variables - { - name = "FLEET_S3_SOFTWARE_INSTALLERS_BUCKET", - value = google_storage_bucket.software_installers.id - }, - { - name = "FLEET_S3_SOFTWARE_INSTALLERS_ACCESS_KEY_ID", - value = google_storage_hmac_key.key.access_id - }, - { - name = "FLEET_S3_SOFTWARE_INSTALLERS_SECRET_ACCESS_KEY", - value = google_storage_hmac_key.key.secret - }, - { - name = "FLEET_S3_SOFTWARE_INSTALLERS_ENDPOINT_URL", - value = "https://storage.googleapis.com" - }, - { - name = "FLEET_S3_SOFTWARE_INSTALLERS_FORCE_S3_PATH_STYLE", - value = "true" - }, - { - name = "FLEET_S3_SOFTWARE_INSTALLERS_REGION", - value = data.google_client_config.current.region - }, - ] + fleet_secrets_env_vars = merge(var.fleet_config.extra_secret_env_vars, { + FLEET_MYSQL_PASSWORD = { + secret = google_secret_manager_secret.database_password.secret_id + version = "latest" + }, + FLEET_SERVER_PRIVATE_KEY = { + secret = google_secret_manager_secret.private_key.secret_id + version = "latest" + } + }) + fleet_env_vars = merge(var.fleet_config.extra_env_vars, { + FLEET_LICENSE_KEY = var.fleet_config.license_key + FLEET_SERVER_FORCE_H2C = "true" + FLEET_MYSQL_PROTOCOL = "tcp" + FLEET_MYSQL_ADDRESS = "${module.mysql.private_ip_address}:3306" + FLEET_MYSQL_USERNAME = var.database_config.database_user + FLEET_MYSQL_DATABASE = var.database_config.database_name + FLEET_REDIS_ADDRESS = "${module.memstore.host}:${module.memstore.port}" + FLEET_REDIS_USE_TLS = "false" + #FLEET_UPGRADES_ALLOW_MISSING_MIGRATIONS = "1" + FLEET_LOGGING_JSON = "true" + FLEET_LOGGING_DEBUG = var.fleet_config.debug_logging + FLEET_SERVER_TLS = "false" + FLEET_S3_SOFTWARE_INSTALLERS_BUCKET = google_storage_bucket.software_installers.id + FLEET_S3_SOFTWARE_INSTALLERS_ACCESS_KEY_ID = google_storage_hmac_key.key.access_id + FLEET_S3_SOFTWARE_INSTALLERS_SECRET_ACCESS_KEY = google_storage_hmac_key.key.secret + FLEET_S3_SOFTWARE_INSTALLERS_ENDPOINT_URL = "https://storage.googleapis.com" + FLEET_S3_SOFTWARE_INSTALLERS_FORCE_S3_PATH_STYLE = "true" + FLEET_S3_SOFTWARE_INSTALLERS_REGION = var.region + }) - # --- Shared VPC Access Configuration Parts --- - # These will be used to construct the block fleet_vpc_network_id = module.vpc.network_id # Use the direct construction for the subnet ID key as discussed fleet_vpc_subnet_id = "fleet-subnet" } -# --- Cloud Run Service (Main Webserver) --- -resource "google_cloud_run_v2_service" "fleet_service" { - name = "${var.prefix}-service" - location = var.region - project = var.project_id - deletion_protection = false - +module "fleet-service" { + source = "GoogleCloudPlatform/cloud-run/google//modules/v2" + version = "~> 0.17" + + service_name = "fleet-api" + project_id = var.project_id + location = var.region + create_service_account = false + service_account = google_service_account.fleet_run_sa.email + enable_prometheus_sidecar = false + cloud_run_deletion_protection = false + + vpc_access = { + network_interfaces = { + network = local.fleet_vpc_network_id + subnetwork = local.fleet_vpc_subnet_id + } + egress = "ALL_TRAFFIC" + } ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER" - timeouts { - create = "300s" - update = "300s" + timeout = "300s" + service_scaling = { + min_instance_count = 0 + } + template_scaling = { + min_instance_count = 0 + max_instance_count = 0 } - template { - service_account = google_service_account.fleet_run_sa.email # Defined in iam.tf - - # Define vpc_access block directly - vpc_access { - network_interfaces { - network = local.fleet_vpc_network_id - subnetwork = local.fleet_vpc_subnet_id - } - egress = "ALL_TRAFFIC" - } - - containers { - image = local.fleet_image_tag - ports { + containers = [ + { + container_image = local.fleet_image_tag + ports = { name = "h2c" container_port = 8080 } - command = ["/bin/sh"] - args = [ - "-c", - "fleet prepare --no-prompt=true db; exec fleet serve" - ] - - startup_probe { - initial_delay_seconds = 10 - timeout_seconds = 5 - period_seconds = 3 + # container_command = ["/bin/sh"] + # container_args = [ + # "-c", + # "fleet prepare --no-prompt=true db; exec fleet serve" + # ] + + startup_probe = { + initial_delay_seconds = 30 + timeout_seconds = 2 + period_seconds = 60 failure_threshold = 3 - tcp_socket { + + tcp_socket = { port = 8080 } } - liveness_probe { - http_get { - path = "/healthz" + + liveness_probe = { + initial_delay_seconds = 30 + timeout_seconds = 2 + failure_threshold = 3 + period_seconds = 60 + http_get = { + path = "/healthz" + http_headers = [] } } - resources { + resources = { limits = local.fleet_resources_limits } - dynamic "env" { - for_each = local.fleet_common_env_vars - content { - name = env.value.name - value = try(env.value.value, null) - dynamic "value_source" { - for_each = try(env.value.value_source, null) != null ? [env.value.value_source] : [] - content { - secret_key_ref { - secret = value_source.value.secret_key_ref.secret - version = value_source.value.secret_key_ref.version - } - } - } - } - } - } - - scaling { - min_instance_count = var.fleet_config.min_instance_count - max_instance_count = var.fleet_config.max_instance_count + env_vars = local.fleet_env_vars + env_secret_vars = local.fleet_secrets_env_vars } - } - - depends_on = [ - google_service_account.fleet_run_sa, - google_secret_manager_secret_version.database_password, - google_secret_manager_secret_version.private_key, ] } # --- Cloud Run Job (Migrations) --- resource "google_cloud_run_v2_job" "fleet_migration_job" { - name = "${var.prefix}-migration-job" + name = "fleet-migration" location = var.region project = var.project_id @@ -215,19 +144,22 @@ resource "google_cloud_run_v2_job" "fleet_migration_job" { resources { limits = local.fleet_resources_limits } - # Define env block directly, iterating over the local list + + dynamic "env" { + for_each = local.fleet_env_vars + content { + name = env.key + value = env.value + } + } dynamic "env" { - for_each = local.fleet_common_env_vars + for_each = local.fleet_secrets_env_vars content { - name = env.value.name - value = try(env.value.value, null) - dynamic "value_source" { - for_each = try(env.value.value_source, null) != null ? [env.value.value_source] : [] - content { - secret_key_ref { - secret = value_source.value.secret_key_ref.secret - version = value_source.value.secret_key_ref.version - } + name = env.key + value_source { + secret_key_ref { + secret = env.value.secret + version = env.value.version } } } @@ -245,13 +177,49 @@ resource "google_cloud_run_v2_job" "fleet_migration_job" { ] } +data "google_client_config" "default" {} + +resource "terracurl_request" "exec" { + count = var.fleet_config.exec_migration ? 1 : 0 + name = "exec-job" + url = "https://run.googleapis.com/v2/${google_cloud_run_v2_job.fleet_migration_job.id}:run" + method = "POST" + headers = { + Authorization = "Bearer ${data.google_client_config.default.access_token}" + Content-Type = "application/json", + } + response_codes = [200] + // no-op destroy + // we don't use terracurl_request data source as that will result in + // repeated job runs on every refresh + destroy_url = "https://run.googleapis.com/v2/${google_cloud_run_v2_job.fleet_migration_job.id}" + destroy_method = "GET" + destroy_response_codes = [200] + destroy_headers = { + Authorization = "Bearer ${data.google_client_config.default.access_token}" + Content-Type = "application/json", + } +} + resource "google_compute_region_network_endpoint_group" "neg" { name = "${var.prefix}-neg" region = var.region project = var.project_id network_endpoint_type = "SERVERLESS" # This type works for Cloud Run v2 services cloud_run { - service = google_cloud_run_v2_service.fleet_service.name # Reference the v2 service name + service = module.fleet-service.service_name } - depends_on = [google_cloud_run_v2_service.fleet_service] + depends_on = [module.fleet-service] +} + +data "google_project" "project" { + project_id = var.project_id } + +resource "google_cloud_run_v2_service_iam_member" "allow_lb_invoker" { + project = var.project_id + location = module.fleet-service.location + name = module.fleet-service.service_name + role = "roles/run.invoker" + member = "allUsers" +} \ No newline at end of file diff --git a/gcp/byo-project/main.tf b/gcp/byo-project/main.tf index abe4d03..552122c 100644 --- a/gcp/byo-project/main.tf +++ b/gcp/byo-project/main.tf @@ -2,8 +2,13 @@ terraform { required_version = "~> 1.11" required_providers { google = { - source = "hashicorp/google" - version = "6.32.0" + source = "hashicorp/google" + version = "6.35.0" + } + + terracurl = { + source = "devops-rob/terracurl" + version = "~> 1.0" } } } diff --git a/gcp/byo-project/outputs.tf b/gcp/byo-project/outputs.tf new file mode 100644 index 0000000..7b67b39 --- /dev/null +++ b/gcp/byo-project/outputs.tf @@ -0,0 +1,89 @@ +output "fleet_application_url" { + description = "The primary URL to access the Fleet application (via the Load Balancer)." + value = "https://${google_dns_record_set.fleet_dns_record.name}" +} + +output "load_balancer_ip_address" { + description = "The external IP address of the HTTP(S) Load Balancer." + value = module.fleet_lb.external_ip +} + +output "cloud_run_service_name" { + description = "The name of the deployed Fleet Cloud Run service." + value = module.fleet-service.service_name +} + +output "cloud_run_service_location" { + description = "The location of the deployed Fleet Cloud Run service." + value = module.fleet-service.location // Check the actual output name +} + +output "mysql_instance_name" { + description = "The name of the Cloud SQL for MySQL instance." + value = module.mysql.instance_name +} + +output "mysql_instance_connection_name" { + description = "The connection name for the Cloud SQL instance (used by Cloud SQL Proxy)." + value = module.mysql.instance_connection_name +} + +output "redis_instance_name" { + description = "The name of the Memorystore for Redis instance." + value = module.memstore.id +} + +output "redis_host" { + description = "The host IP address of the Memorystore for Redis instance." + value = module.memstore.host +} + +output "redis_port" { + description = "The port number of the Memorystore for Redis instance." + value = module.memstore.port +} + +output "software_installers_bucket_name" { + description = "The name of the GCS bucket for Fleet software installers." + value = google_storage_bucket.software_installers.name +} + +output "software_installers_bucket_url" { + description = "The gsutil URL of the GCS bucket for Fleet software installers." + value = google_storage_bucket.software_installers.url +} + +output "fleet_service_account_email" { + description = "The email address of the service account used by the Fleet Cloud Run service." + value = google_service_account.fleet_run_sa.email +} + +output "dns_managed_zone_name" { + description = "The name of the Cloud DNS managed zone created for Fleet." + value = google_dns_managed_zone.fleet_dns_zone.name +} + +output "dns_managed_zone_name_servers" { + description = "The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these." + value = google_dns_managed_zone.fleet_dns_zone.name_servers +} + +output "vpc_network_name" { + description = "The name of the VPC network created." + value = module.vpc.network_name +} + +output "vpc_network_self_link" { + description = "The self-link of the VPC network created." + value = module.vpc.network_self_link +} + +output "vpc_subnets_names" { + description = "List of subnet names created in the VPC." + value = module.vpc.subnets_names +} + +output "cloud_router_name" { + description = "The name of the Cloud Router created for NAT." + value = module.cloud_router.router.name +} diff --git a/gcp/byo-project/variables.tf b/gcp/byo-project/variables.tf index c46710f..532a2e0 100644 --- a/gcp/byo-project/variables.tf +++ b/gcp/byo-project/variables.tf @@ -91,21 +91,30 @@ variable "vpc_config" { variable "fleet_config" { type = object({ installers_bucket_name = string - image_tag = string - fleet_cpu = string - fleet_memory = string - debug_logging = bool - license_key = optional(string) - min_instance_count = number - max_instance_count = number + image_tag = string + fleet_cpu = string + fleet_memory = string + debug_logging = bool + license_key = optional(string) + min_instance_count = number + max_instance_count = number + exec_migration = bool + extra_env_vars = optional(map(string)) + extra_secret_env_vars = optional(map(object({ + secret = string + version = string + }))) }) default = { - image_tag = "fleetdm/fleet:v4.67.3" - installers_bucket_name = "fleet-installers-gcp-test-1" // todo fix - fleet_cpu = "1000m" - fleet_memory = "4096Mi" - debug_logging = false - min_instance_count = 1 - max_instance_count = 5 + image_tag = "fleetdm/fleet:v4.67.3" + installers_bucket_name = "fleet-installers-gcp-test-1" // todo fix + fleet_cpu = "1000m" + fleet_memory = "4096Mi" + debug_logging = false + min_instance_count = 1 + max_instance_count = 5 + exec_migration = true + extra_env_vars = {} + extra_secret_env_vars = {} } } diff --git a/gcp/main.tf b/gcp/main.tf index 52d1b12..338a4b1 100644 --- a/gcp/main.tf +++ b/gcp/main.tf @@ -3,7 +3,7 @@ terraform { required_providers { google = { source = "hashicorp/google" - version = "6.32.0" + version = "6.35.0" } } } @@ -54,4 +54,10 @@ module "fleet" { project_id = module.project_factory.project_id dns_record_name = var.dns_record_name dns_zone_name = var.dns_zone_name + vpc_config = var.vpc_config + fleet_config = var.fleet_config + cache_config = var.cache_config + database_config = var.database_config + region = var.region + location = var.location } \ No newline at end of file diff --git a/gcp/outputs.tf b/gcp/outputs.tf new file mode 100644 index 0000000..2fdc2e0 --- /dev/null +++ b/gcp/outputs.tf @@ -0,0 +1,89 @@ +output "fleet_application_url" { + description = "The primary URL to access the Fleet application (via the Load Balancer)." + value = module.fleet.fleet_application_url +} + +output "load_balancer_ip_address" { + description = "The external IP address of the HTTP(S) Load Balancer." + value = module.fleet.load_balancer_ip_address +} + +output "cloud_run_service_name" { + description = "The name of the deployed Fleet Cloud Run service." + value = module.fleet.cloud_run_service_name +} + +output "cloud_run_service_location" { + description = "The location of the deployed Fleet Cloud Run service." + value = module.fleet.cloud_run_service_location +} + +output "mysql_instance_name" { + description = "The name of the Cloud SQL for MySQL instance." + value = module.fleet.mysql_instance_name +} + +output "mysql_instance_connection_name" { + description = "The connection name for the Cloud SQL instance (used by Cloud SQL Proxy)." + value = module.fleet.mysql_instance_connection_name +} + +output "redis_instance_name" { + description = "The name of the Memorystore for Redis instance." + value = module.fleet.redis_instance_name +} + +output "redis_host" { + description = "The host IP address of the Memorystore for Redis instance." + value = module.fleet.redis_host +} + +output "redis_port" { + description = "The port number of the Memorystore for Redis instance." + value = module.fleet.redis_port +} + +output "software_installers_bucket_name" { + description = "The name of the GCS bucket for Fleet software installers." + value = module.fleet.software_installers_bucket_name +} + +output "software_installers_bucket_url" { + description = "The gsutil URL of the GCS bucket for Fleet software installers." + value = module.fleet.software_installers_bucket_url +} + +output "fleet_service_account_email" { + description = "The email address of the service account used by the Fleet Cloud Run service." + value = module.fleet.fleet_service_account_email +} + +output "dns_managed_zone_name" { + description = "The name of the Cloud DNS managed zone created for Fleet." + value = module.fleet.dns_managed_zone_name +} + +output "dns_managed_zone_name_servers" { + description = "The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these." + value = module.fleet.dns_managed_zone_name_servers +} + +output "vpc_network_name" { + description = "The name of the VPC network created." + value = module.fleet.vpc_network_name +} + +output "vpc_network_self_link" { + description = "The self-link of the VPC network created." + value = module.fleet.vpc_network_self_link +} + +output "vpc_subnets_names" { + description = "List of subnet names created in the VPC." + value = module.fleet.vpc_subnets_names +} + +output "cloud_router_name" { + description = "The name of the Cloud Router created for NAT." + value = module.fleet.cloud_router_name +} diff --git a/gcp/readme.md b/gcp/readme.md new file mode 100644 index 0000000..0dd8b6b --- /dev/null +++ b/gcp/readme.md @@ -0,0 +1,247 @@ +# Terraform Google Cloud FleetDM Deployment + +This Terraform project automates the deployment of Fleet Device Management (FleetDM) on the Google Cloud Platform (GCP). It provisions a new GCP project (or uses an existing one if configured) and deploys all necessary infrastructure components, including: + +* A new Google Cloud Project (via `terraform-google-modules/project-factory`) +* VPC Network and Subnets +* Cloud NAT for outbound internet access +* Cloud SQL for MySQL (database for Fleet) +* Memorystore for Redis (caching for Fleet) +* Google Cloud Storage (GCS) bucket (for Fleet software installers, S3-compatible access configured) +* Google Secret Manager (for storing sensitive data like database passwords and Fleet server private key) +* Cloud Run v2 Service (for the main Fleet application) +* Cloud Run v2 Job (for database migrations) +* External HTTP(S) Load Balancer with managed SSL certificate +* Cloud DNS for managing the Fleet endpoint +* Appropriate IAM service accounts and permissions + +## Prerequisites + +1. **Terraform:** Version `~> 1.11` (as specified in `main.tf`). Install from [terraform.io](https://www.terraform.io/downloads.html). +2. **Google Cloud SDK (`gcloud`):** Installed and configured. Install from [cloud.google.com/sdk](https://cloud.google.com/sdk/docs/install). +3. **GCP Account & Permissions:** + * A Google Cloud Organization ID. + * A Billing Account ID. + * Credentials configured for Terraform to interact with GCP. This typically means running `gcloud auth application-default login` with a user account that has: + * `Organization Administrator` or `Folder Creator` & `Project Creator` roles at the Organization/Folder level (for the `project-factory` module). + * `Billing Account User` role on the Billing Account. + * Once the project is created, subsequent applies for resources within the project will generally require `Owner` or `Editor` on the provisioned project. + * Alternatively, use a Service Account with appropriate permissions. +4. **Registered Domain Name:** You will need a domain name that you can manage DNS records for. This project will create a Cloud DNS Managed Zone for this domain (or a subdomain). +5. **(Optional) FleetDM License Key:** If you have a premium FleetDM license, you can provide it. + +## Configuration + +1. **Clone the repository (if applicable) or ensure you have all the files.** +2. **Create a `terraform.tfvars` file:** + Copy `terraform.tfvars.example` (you'll need to create this, see example below) to `terraform.tfvars` and populate it with your specific values. + + **Example `terraform.tfvars.example`:** + ```hcl + # Required: GCP Organization and Billing + org_id = "YOUR_ORGANIZATION_ID" # e.g., "123456789012" + billing_account_id = "YOUR_BILLING_ACCOUNT_ID" # e.g., "012345-6789AB-CDEF01" + + # Required: DNS Configuration + # The public DNS zone name (e.g., "example.com." - note the trailing dot) + # This project will create/manage this zone in Cloud DNS. + dns_zone_name = "your-domain.com." + # The fully qualified domain name for your Fleet instance (e.g., "fleet.your-domain.com.") + dns_record_name = "fleet.your-domain.com." + + # Optional: Project Configuration + project_name = "my-fleet-project" # Name for the new GCP project + random_project_id = true # Set to false if you want to use project_name as project_id (must be globally unique) + + # Optional: Labels + labels = { + application = "fleetdm" + environment = "production" + owner = "devops-team" + } + + # --- byo-project module specific variables (passed through from root) --- + # You can override defaults from byo-project/variables.tf here if needed, + # or set values for variables that don't have defaults. + + # Example: FleetDM License Key (if you have one) + # fleet_config = { + # license_key = "YOUR_FLEET_LICENSE_KEY" + # # You can also override other fleet_config defaults here: + # # image_tag = "fleetdm/fleet:latest" + # # installers_bucket_prefix = "my-fleet-installers" + # # fleet_cpu = "1000m" + # # fleet_memory = "4096Mi" + # # debug_logging = false + # # min_instance_count = 1 + # # max_instance_count = 3 + # } + + ``` + + **Key Variables to Set:** + * `org_id`: Your GCP Organization ID. + * `billing_account_id`: Your GCP Billing Account ID. + * `dns_zone_name`: The DNS zone that will be created/managed in Cloud DNS (e.g., `mydomain.com.`). **Must end with a dot.** + * `dns_record_name`: The specific DNS record for Fleet (e.g., `fleet.mydomain.com.`). **Must end with a dot.** + * `project_name`: A descriptive name for the project to be created. + * `fleet_config.license_key` (Optional, inside the `fleet_config` object): Your FleetDM license key if you have one. + + Review `variables.tf` and `byo-project/variables.tf` for all available options and their defaults. + +## Deployment Steps + +1. **Authenticate with GCP:** + Ensure your `gcloud` CLI is authenticated with the account having the necessary permissions. + ```bash + gcloud auth application-default login + ``` + If you need to specify a project for ADC (though project factory creates one): + ```bash + gcloud config set project YOUR_QUOTA_PROJECT_ID # A project where gcloud can store credentials + gcloud auth application-default set-quota-project YOUR_QUOTA_PROJECT_ID + ``` + +2. **Initialize Terraform:** + Navigate to the root directory of the project (where `main.tf` is located) and run: + ```bash + terraform init + ``` + +3. **Plan the Deployment:** + Review the changes Terraform will make: + ```bash + terraform plan -out=tfplan + ``` + +4. **Apply the Configuration:** + Provision the resources: + ```bash + terraform apply tfplan + ``` + This process can take a significant amount of time (15-30 minutes or more), especially for the initial project creation, API enablement, and Cloud SQL instance provisioning. + +5. **Run Database Migrations:** + After `terraform apply` completes, the Fleet application is deployed but the database schema needs to be initialized/migrated. The project creates a Cloud Run Job (`fleet-migration-job`) for this purpose. You need to execute this job manually. + + * **Find the Job Name and Location:** + The job name will be like `fleet-migration-job` (if prefix is `fleet`) or `-migration-job` in the region you deployed to (default `us-central1`). The project ID will be the one created by project factory. You can find the exact name and project ID in the Terraform output or by inspecting resources in the GCP console. + + * **Execute the Job using `gcloud`:** + Replace `[JOB_NAME]`, `[REGION]`, and `[PROJECT_ID]` with your actual values. + ```bash + PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1) # Adjust filter if your project_name is different + JOB_NAME="fleet-migration-job" # Or as configured by 'prefix' variable + REGION="us-central1" # Or as configured by 'region' variable + + gcloud run jobs execute ${JOB_NAME} \ + --region ${REGION} \ + --project ${PROJECT_ID} \ + --wait # Use --wait to see the job complete + ``` + If you don't have `terraform output -raw project_id` (because it's not defined in `outputs.tf`), you'll need to find the project ID manually, e.g., from the GCP console or by listing projects with `gcloud projects list`. + + * **Verify Job Completion:** + You can monitor the job's progress in the GCP Console under Cloud Run > Jobs. Ensure it completes successfully. + +6. **Update DNS (if not using Cloud DNS for the parent zone):** + If `dns_zone_name` (e.g., `fleet.your-domain.com.`) is a new zone created by this Terraform, and your parent domain (e.g., `your-domain.com.`) is managed elsewhere (e.g., GoDaddy, Cloudflare), you need to delegate this new zone. + * Get the Name Servers (NS) records for the newly created `google_dns_managed_zone` (`${var.prefix}-zone`): + ```bash + # First, get the project ID and zone name if you don't know them + PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1) + DNS_ZONE_INTERNAL_NAME="fleet-zone" # Or as configured by 'prefix' variable + + gcloud dns managed-zones describe ${DNS_ZONE_INTERNAL_NAME} \ + --project=${PROJECT_ID} \ + --format="value(nameServers)" + ``` + * Add these NS records to your parent domain's DNS settings at your domain registrar or DNS provider. + + If `dns_zone_name` (e.g., `your-domain.com.`) was an *existing* zone you own and manage via Cloud DNS in this project, Terraform would add records to it directly. The current setup *creates* the zone, so delegation is key if the parent is external. + +## Accessing FleetDM + +Once the deployment is complete, database migrations have run successfully, and DNS has propagated (which can take a few minutes): + +* Open your web browser and navigate to the `dns_record_name` you configured (e.g., `https://fleet.your-domain.com`). +* You should be greeted by the FleetDM setup page or login screen. + +## Key Resources Created (within the `byo-project` module) + +* **VPC Network (`fleet-network`):** Custom network for Fleet resources. +* **Subnet (`fleet-subnet`):** Subnetwork within the VPC. +* **Cloud Router & NAT (`fleet-cloud-router`, `fleet-vpc-nat`):** For outbound internet access from Cloud Run and other private resources. +* **Cloud SQL for MySQL (`fleet-mysql-xxxx`):** Managed MySQL database. +* **Memorystore for Redis (`fleet-cache`):** Managed Redis instance. +* **GCS Bucket (`fleet-installers-xxxx`):** For S3-compatible storage. +* **Secret Manager Secrets:** + * `fleet-db-password-xxxx`: Stores the generated MySQL user password. + * `fleet-private-key-xxxx`: Stores a generated private key for Fleet server. +* **Service Account (`fleet-run-sa`):** Used by Cloud Run services/jobs with necessary permissions. +* **Cloud Run Service (`fleet-service`):** Hosts the main Fleet application. +* **Cloud Run Job (`fleet-migration-job`):** For database migrations. +* **Network Endpoint Group (`fleet-neg`):** Connects the Load Balancer to the Cloud Run service. +* **Cloud DNS Managed Zone (`fleet-zone`):** Manages DNS records for `dns_zone_name`. +* **External HTTP(S) Load Balancer (`fleet-lb`):** Provides public access to Fleet via HTTPS. +* **DNS A Record:** Points `dns_record_name` to the Load Balancer's IP. + +(Note: `xxxx` indicates a random suffix added by some modules/resources for uniqueness.) + +## Cleaning Up + +To destroy all resources created by this Terraform project: +1. Navigate to the root directory of the project. +2. Run the destroy command: + ```bash + terraform destroy + ``` +3. Confirm by typing `yes` when prompted. + +## Important Considerations + +* **Costs:** Running these resources on GCP will incur costs. Be sure to understand the pricing for Cloud SQL, Memorystore, Cloud Run, Load Balancing, GCS, etc. Shut down resources with `terraform destroy` if they are no longer needed. +* **Permissions:** The user or service account running Terraform needs extensive permissions, especially for project creation. For ongoing management within the project, a role like `Project Editor` or more granular roles might suffice. +* **FleetDM Configuration:** This Terraform setup provisions the infrastructure. Further FleetDM application configuration (e.g., SSO, SMTP, agent options) will need to be done through the Fleet UI or API after deployment. +* **Security:** + * Review IAM permissions granted. + * The Load Balancer is configured for HTTPS and redirects HTTP. + * Cloud SQL and Memorystore are not publicly accessible and are connected via Private Service Access. + * Consider enabling IAP (Identity-Aware Proxy) on the Load Balancer for an additional layer of access control if needed (disabled by default in `loadbalancer.tf`). +* **Scalability:** Cloud Run scaling parameters (`min_instance_count`, `max_instance_count`) and database/cache tier can be adjusted in `variables.tf` (via `fleet_config`, `database_config`, `cache_config`) to suit your load. + + +### Networking Diagram +```mermaid +graph TD + subgraph External + Internet[(Internet)] + Users[Web Console / fleetd agent] + GitHub[(GitHub - Vulnerability Resources)] + end + + subgraph "Google Cloud Platform (GCP)" + subgraph VPC [VPC] + direction LR + subgraph PublicFacing [Public Zone] + LB[Global Load Balancer] + end + subgraph PrivateZone [Private Zone] + CloudRun[Cloud Run: Fleet App] + CloudSQL[Cloud SQL for MySQL] + Redis[Memorystore for Redis] + NAT[Cloud NAT] + end + + CloudRun --> Redis + CloudRun --> CloudSQL + CloudRun --> NAT + end + end + + Users -- "fleet.yourdomain.com" --> Internet + Internet -- "fleet.yourdomain.com" --> LB + LB --> CloudRun + NAT --> GitHub + +``` \ No newline at end of file diff --git a/gcp/refactor-plan.md b/gcp/refactor-plan.md deleted file mode 100644 index 7f483b9..0000000 --- a/gcp/refactor-plan.md +++ /dev/null @@ -1,150 +0,0 @@ -**Analysis of the Current Terraform Configuration** - -1. **Overall Structure:** The Terraform code is reasonably well-structured, breaking resources down into logical files (`vpc.tf`, `mysql.tf`, `redis.tf`, `cloud_run.tf`, `loadbalancer.tf`, `storage.tf`, etc.). This makes it maintainable. - -2. **Core Components:** - * **Web Server (Fleet):** Deployed using Cloud Run (`google_cloud_run_service`). This aligns well with the serverless goal. It uses environment variables extensively for configuration, including credentials fetched directly from Secret Manager (`value_from`), which is a good practice. It connects to the database via the Cloud SQL connection name annotation and uses a Serverless VPC Access connector for private network access (Redis, Private IP SQL). - * **Redis Cache:** Deployed using Memorystore for Redis (`google_redis_instance`) with private service access. Good choice for managed Redis. - * **Database (MySQL):** Deployed using Cloud SQL (`module "fleet-mysql"`), configured for private IP access. Uses a Terraform module for abstraction. - * **Networking:** A custom VPC, subnet, Serverless VPC Access Connector, Private Service Access for managed services, and Cloud NAT are set up using Terraform modules. This provides the necessary private connectivity. - * **Load Balancing:** An External HTTPS Load Balancer (`module "lb-http"`) is configured using a Serverless Network Endpoint Group (NEG) pointing to the Cloud Run service. It handles SSL termination with managed certificates and DNS integration. - * **Secrets Management:** Google Secret Manager is used to store the database password and a Fleet private key, accessed securely by Cloud Run. - * **Storage:** A GCS bucket is created for "software installers", and an HMAC key is generated for S3-compatible access, passed as environment variables to Cloud Run. - -3. **Serverless & Minimal Configuration Focus:** - * **Successes:** Cloud Run, Memorystore, Cloud SQL (with private IP), Serverless NEG, Managed SSL Certificates, Secret Manager integration all fit the serverless/managed/minimal config goal well. - * **Areas for Review:** The setup still requires explicit VPC, Subnet, VPC Connector, PSA, and NAT configuration. While necessary for private connectivity with this architecture, newer features might simplify parts of this. - -4. **Potential Issues & Outdated Practices (relative to today):** - * **Provider/Module Versions:** `hashicorp/google` v4.51.0, `terraform-google-modules/network` v4.1.0, `GoogleCloudPlatform/lb-http` v6.2.0, `GoogleCloudPlatform/sql-db` v9.0.0, `terraform-google-modules/cloud-router` v6.0 are all significantly outdated. Newer versions offer bug fixes, performance improvements, new features, and support for the latest GCP APIs. - * **Cloud Run Service Account:** The Cloud Run service runs as the *default compute service account* (`data.google_compute_default_service_account.default.email`). This is generally discouraged as this account often has broad permissions. A dedicated service account with least-privilege permissions is recommended. - * **Fleet Image Version:** The Fleet image (`fleetdm/fleet:v4.66.0`) is hardcoded and old. This prevents easy updates and runs outdated software. - * **`google-beta` Provider:** Its necessity should be re-evaluated after updating the main `google` provider. Often, features graduate from beta. - * **HMAC Key Usage:** While functional and required if the application *only* speaks S3 API, if Fleet *could* use native GCS authentication (via Application Default Credentials), using the Cloud Run service account directly would be simpler and avoid managing HMAC keys. However, the env vars (`FLEET_S3_*`) strongly suggest S3 compatibility is expected, making HMAC necessary. - * **Random Pet for Secret Names:** Functional, but less predictable than using `${var.prefix}` or similar structures. Might make finding secrets harder outside of Terraform state. - -**Refactoring Suggestions** - -Here are suggestions to modernize the stack, leveraging newer GCP features and Terraform practices, while maintaining the minimal configuration goal: - -1. **Update Dependencies:** - * **Terraform:** Ensure you are running a recent Terraform version (e.g., 1.5.x or later). - * **Providers:** Update the `hashicorp/google` provider to the latest 5.x version. Check the Terraform Registry for the latest. Remove the `google-beta` provider unless explicitly required by a resource/feature *after* updating the main provider. - * **Modules:** Update all `terraform-google-modules/*` and `GoogleCloudPlatform/*` modules to their latest stable versions. Check their respective repositories/Terraform Registry for release notes and potential breaking changes. Newer module versions often have better integration and more features. - * Example `main.tf`: - ```terraform - terraform { - required_version = "~> 1.5" # Or later - required_providers { - google = { - source = "hashicorp/google" - version = "~> 5.10" # Check latest - } - # google-beta likely not needed anymore - } - } - - provider "google" { - project = var.project_id - region = var.region - } - ``` - * Update `version` constraints in `mysql.tf`, `vpc.tf`, `loadbalancer.tf` etc. - -2. **Implement Dedicated Service Account for Cloud Run:** - * Create a new service account specifically for the Fleet Cloud Run service. - * Grant this SA the necessary roles (e.g., `roles/secretmanager.secretAccessor`, `roles/cloudsql.client`, potentially roles for logging/monitoring if needed). - * Update the Secret Manager IAM bindings (`google_secret_manager_secret_iam_member`) to grant access to this *new* SA instead of the default compute SA. - * Modify the `google_cloud_run_service` definition to use this SA. - * Example additions/modifications: - ```terraform - # In a suitable file like iam.tf or cloud_run.tf - resource "google_service_account" "fleet_run_sa" { - account_id = "${var.prefix}-fleet-run-sa" - display_name = "Service Account for Fleet Cloud Run" - } - - resource "google_project_iam_member" "fleet_run_sa_sql_client" { - project = var.project_id - role = "roles/cloudsql.client" - member = "serviceAccount:${google_service_account.fleet_run_sa.email}" - } - - # Modify secret IAM bindings - resource "google_secret_manager_secret_iam_member" "secret-access" { - secret_id = google_secret_manager_secret.secret.id - role = "roles/secretmanager.secretAccessor" - # Use the new SA email - member = "serviceAccount:${google_service_account.fleet_run_sa.email}" - depends_on = [google_secret_manager_secret.secret] - } - - resource "google_secret_manager_secret_iam_member" "private-key-access" { - secret_id = google_secret_manager_secret.private_key.id - role = "roles/secretmanager.secretAccessor" - # Use the new SA email - member = "serviceAccount:${google_service_account.fleet_run_sa.email}" - depends_on = [google_secret_manager_secret.private_key] - } - - # Modify cloud_run.tf - resource "google_cloud_run_service" "default" { - # ... existing config ... - template { - spec { - # Add this line - service_account_name = google_service_account.fleet_run_sa.email - containers { - # ... existing container config ... - } - } - # ... existing metadata ... - } - # ... rest of config ... - } - ``` - -3. **Parameterize Fleet Image Version:** - * Avoid hardcoding the image version in `variables.tf`. Allow it to be overridden or default to a known recent tag or even `latest` (with caution for production). - * Example `variables.tf`: - ```terraform - variable "image" { - # Update default to a recent stable version - default = "fleetdm/fleet:stable" # Or specific e.g., fleetdm/fleet:v4.XX.Y - description = "Docker image for the Fleet backend service." - } - ``` - -4. **Simplify VPC and Connectivity (Leverage Module Updates):** - * After updating the `terraform-google-modules/network/google` module, check if features like Serverless VPC Access Connector creation, Private Service Access enablement, and Cloud NAT can be configured more directly within the main VPC module definition, potentially reducing the need for separate modules or simplifying their configuration. Consult the module's documentation for the latest usage patterns. - -5. **Update Database/Redis Versions (Optional):** - * Check Cloud SQL and Memorystore documentation for the latest supported versions of MySQL (e.g., `MYSQL_8_0_36` if specific minor versions are beneficial) and Redis. Update `var.db_version` if appropriate and compatible with Fleet. - -6. **Refine Secret Naming (Optional):** - * Consider replacing `random_pet` with a more predictable naming scheme if desired for easier identification in the GCP console. - * Example `cloud_run.tf`: - ```terraform - resource "random_id" "secret_suffix" { - byte_length = 4 - } - - resource "google_secret_manager_secret" "secret" { - # Use prefix and random_id for predictable but unique names - secret_id = "${var.prefix}-fleet-db-password-${random_id.secret_suffix.hex}" - # ... rest ... - } - - resource "google_secret_manager_secret" "private_key" { - secret_id = "${var.prefix}-fleet-private-key-${random_id.secret_suffix.hex}" - # ... rest ... - } - ``` - -7. **Consider Cloud Run V2 Resource (Optional Evaluation):** - * While the current `google_cloud_run_service` likely uses the V2 API backend, review the explicit `google_cloud_run_v2_service` resource introduced in later provider versions. It might offer a cleaner syntax or expose newer features not available via annotations (though most common features *are* available via annotations). For this specific setup, sticking with the updated `google_cloud_run_service` is likely sufficient unless a V2-specific feature is needed. - -8. **Review Load Balancer Module Configuration:** - * Update the `GoogleCloudPlatform/lb-http/google` module and review its input variables. Newer versions might offer simplified configuration for common patterns, better security defaults (e.g., default security policies), or improved logging/monitoring integration. - -By implementing these changes, particularly updating dependencies and using a dedicated service account, you'll significantly modernize the Terraform configuration, improve security, and make it easier to manage updates while preserving the core serverless/managed architecture. diff --git a/gcp/variables.tf b/gcp/variables.tf index e228736..be4b9e0 100644 --- a/gcp/variables.tf +++ b/gcp/variables.tf @@ -12,7 +12,7 @@ variable "billing_account_id" { variable "labels" { description = "resource labels" - default = {application = "fleet"} + default = { application = "fleet" } type = map(string) } @@ -32,4 +32,119 @@ variable "dns_record_name" { variable "random_project_id" { default = true -} \ No newline at end of file +} +variable "location" { + description = "The general location for resources, e.g., 'us' for GCS buckets." + type = string + default = "us" +} + +variable "region" { + description = "The GCP region for regional resources." + type = string + default = "us-central1" +} + +variable "prefix" { + description = "A prefix used for naming resources within the byo-project module." + type = string + default = "fleet" +} + +variable "cache_config" { + description = "Configuration for the Memorystore (Redis) instance." + type = object({ + name = string + tier = string + engine_version = string + connect_mode = string + memory_size = number + }) + default = { + name = "fleet-cache" + tier = "STANDARD_HA" + engine_version = null // defaults to version 7 + connect_mode = "PRIVATE_SERVICE_ACCESS" + memory_size = 1 + } +} + +variable "database_config" { + description = "Configuration for the Cloud SQL (MySQL) instance." + type = object({ + name = string + database_name = string + database_user = string + collation = string + charset = string + deletion_protection = bool + database_version = string + tier = string + }) + default = { + name = "fleet-mysql" + database_name = "fleet" + database_user = "fleet" + collation = "utf8mb4_unicode_ci" + charset = "utf8mb4" + deletion_protection = false + database_version = "MYSQL_8_0" + tier = "db-n1-standard-1" + } +} + +variable "vpc_config" { + description = "Configuration for the VPC network and subnets." + type = object({ + network_name = string + subnets = list(object({ + subnet_name = string + subnet_ip = string + subnet_region = string + subnet_private_access = bool + })) + }) + default = { + network_name = "fleet-network" + subnets = [ + { + subnet_name = "fleet-subnet" + subnet_ip = "10.10.10.0/24" + subnet_region = "us-central1" + subnet_private_access = true + } + ] + } +} + +variable "fleet_config" { + description = "Configuration for the Fleet application deployment." + type = object({ + installers_bucket_name = string + image_tag = string + fleet_cpu = string + fleet_memory = string + debug_logging = bool + license_key = optional(string) + min_instance_count = number + max_instance_count = number + exec_migration = bool + extra_env_vars = optional(map(string)) + extra_secret_env_vars = optional(map(object({ + secret = string + version = string + }))) + }) + default = { + image_tag = "fleetdm/fleet:v4.67.3" + installers_bucket_name = "fleet-installers-gcp-test-1" # Bucket names must be globally unique + fleet_cpu = "1000m" + fleet_memory = "4096Mi" + debug_logging = false + min_instance_count = 1 + max_instance_count = 5 + exec_migration = true + extra_env_vars = {} + extra_secret_env_vars = {} + } +} diff --git a/gcptest/.terraform.lock.hcl b/gcptest/.terraform.lock.hcl deleted file mode 100644 index c43b1f4..0000000 --- a/gcptest/.terraform.lock.hcl +++ /dev/null @@ -1,102 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/google" { - version = "6.32.0" - constraints = ">= 3.33.0, >= 3.43.0, >= 3.83.0, >= 4.25.0, >= 4.28.0, >= 4.51.0, >= 4.64.0, >= 4.74.0, >= 5.38.0, >= 5.41.0, >= 6.1.0, >= 6.19.0, 6.32.0, < 7.0.0" - hashes = [ - "h1:oAGdA7OWiM1F46yt4g6SoXmPKle5cmPCIMRpm7MaM6U=", - "zh:1e8146395e4fc462b9d85150c253042844207a8491e9fa5751894b6d7a5d1b26", - "zh:2e8f0eeedd4e156ba0958078cd9cf33bad7a60529b44af57dc401b7b082753d2", - "zh:439e1667e2499bfa2235f572c85dcae2f5cf0002ce859371f64cdaec9664c171", - "zh:45b14d6f2e701d8c614d96cf52e26c33e47c7248ca86e9d3bf8425db527b3a84", - "zh:59fc8edcf17a8f45aa6eeef1370a0e1c914758dede5bbb51f783553c878e3811", - "zh:614f7cf5e443fa8f575e739b3ff37c3d986f0b1eb91f4c63579008e5ab3fcc23", - "zh:72956d2425883ce84d010a6f3fb64f74ba4528bd0b3f4990d49c4a37098633f5", - "zh:7f8218e8ca484749b907f5cfedfc9ce605d2eb5fa4088c948bd365e4d014001f", - "zh:8e576976de335e5c6a87feefe285a54881784fd60a664fefc2e9d1d007c38b2a", - "zh:b12f7873063ab121b6330a18158f47601b088d518cb5e6bc7be05f9dedf62326", - "zh:f21580639abb248e47838bb0bed325409cf89688b99629066f611f0085387b80", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - -provider "registry.terraform.io/hashicorp/google-beta" { - version = "6.35.0" - constraints = ">= 3.43.0, >= 4.11.0, >= 4.64.0, >= 5.38.0, >= 5.41.0, >= 6.1.0, >= 6.19.0, < 7.0.0" - hashes = [ - "h1:Q1VXolwlo9TM5IatjsII9d5oiwxEb3tzpGUDJQiX9OE=", - "zh:119b4d11dbaff065dc9e91d11d0bfde3076ee21313a61eeee459928ae20ca366", - "zh:137fda0ed8d2d4588021c8f18e61ee0d5219d4f5d95821ee65a1cda3d3574d2c", - "zh:280a5ea88053511443c1b6b2270c415b46df6a7b2ab4e87889d64668c0840440", - "zh:65bd3715520885330cb55289e62baf5354f75de039137657c3b1cf1a5f9761d4", - "zh:7099ca906f54888131da1ceb0a25444b4a9e5a533341cba283d874ef144c51ac", - "zh:b3b1f093f177cf5166b0e38257e8bc0e039cdf27f28693004304c3de387df165", - "zh:b52eefd0a02a87ce1ff5e87026adfe7c693c7e6fa8bfad0e35f70b556486e103", - "zh:bbec0ab75da4d3a972fe2f0bc28a0be8916a8b167d7d889ff6f4611b4536b4a9", - "zh:c7638b10aecfe1659c9c829de52c6b83b58368c1d55c49ebc8945ef289d8f2cf", - "zh:c92260e7b06f5394f101471d0c19759af620e38d145fa54b104d6ed3a68bf437", - "zh:f4bd6cd39368d7a49ffedbbe03b49750316285cd48987a945d3bdc9a4dfb8cce", - "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", - ] -} - -provider "registry.terraform.io/hashicorp/null" { - version = "3.2.4" - constraints = ">= 2.1.0, ~> 3.1" - hashes = [ - "h1:hkf5w5B6q8e2A42ND2CjAvgvSN3puAosDmOJb3zCVQM=", - "zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43", - "zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a", - "zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991", - "zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f", - "zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e", - "zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615", - "zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442", - "zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5", - "zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f", - "zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f", - ] -} - -provider "registry.terraform.io/hashicorp/random" { - version = "3.7.2" - constraints = ">= 2.2.0, ~> 3.1" - hashes = [ - "h1:356j/3XnXEKr9nyicLUufzoF4Yr6hRy481KIxRVpK0c=", - "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", - "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", - "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", - "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", - "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", - "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", - "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", - "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", - "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", - "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", - ] -} - -provider "registry.terraform.io/hashicorp/time" { - version = "0.13.1" - constraints = ">= 0.5.0" - hashes = [ - "h1:+W+DMrVoVnoXo3f3M4W+OpZbkCrUn6PnqDF33D2Cuf0=", - "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", - "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", - "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", - "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", - "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", - "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", - "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", - "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", - "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", - "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", - "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", - "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", - ] -} diff --git a/gcptest/main.tf b/gcptest/main.tf deleted file mode 100644 index 412b0ce..0000000 --- a/gcptest/main.tf +++ /dev/null @@ -1,7 +0,0 @@ -module "fleet" { - source = "../gcp" - billing_account_id = "018189-3756B2-E586C1" - org_id = "885948355217" - dns_record_name = "dogfoodgcp.fleetdm.com." - dns_zone_name = "dogfoodgcp.fleetdm.com." -} \ No newline at end of file From 9387e46799538dbe453ee7d74d5e231379a0ffaa Mon Sep 17 00:00:00 2001 From: Benjamin Edwards Date: Tue, 20 May 2025 01:28:22 -0400 Subject: [PATCH 3/3] remove duplicate readme --- gcp/readme.md | 247 -------------------------------------------------- 1 file changed, 247 deletions(-) delete mode 100644 gcp/readme.md diff --git a/gcp/readme.md b/gcp/readme.md deleted file mode 100644 index 0dd8b6b..0000000 --- a/gcp/readme.md +++ /dev/null @@ -1,247 +0,0 @@ -# Terraform Google Cloud FleetDM Deployment - -This Terraform project automates the deployment of Fleet Device Management (FleetDM) on the Google Cloud Platform (GCP). It provisions a new GCP project (or uses an existing one if configured) and deploys all necessary infrastructure components, including: - -* A new Google Cloud Project (via `terraform-google-modules/project-factory`) -* VPC Network and Subnets -* Cloud NAT for outbound internet access -* Cloud SQL for MySQL (database for Fleet) -* Memorystore for Redis (caching for Fleet) -* Google Cloud Storage (GCS) bucket (for Fleet software installers, S3-compatible access configured) -* Google Secret Manager (for storing sensitive data like database passwords and Fleet server private key) -* Cloud Run v2 Service (for the main Fleet application) -* Cloud Run v2 Job (for database migrations) -* External HTTP(S) Load Balancer with managed SSL certificate -* Cloud DNS for managing the Fleet endpoint -* Appropriate IAM service accounts and permissions - -## Prerequisites - -1. **Terraform:** Version `~> 1.11` (as specified in `main.tf`). Install from [terraform.io](https://www.terraform.io/downloads.html). -2. **Google Cloud SDK (`gcloud`):** Installed and configured. Install from [cloud.google.com/sdk](https://cloud.google.com/sdk/docs/install). -3. **GCP Account & Permissions:** - * A Google Cloud Organization ID. - * A Billing Account ID. - * Credentials configured for Terraform to interact with GCP. This typically means running `gcloud auth application-default login` with a user account that has: - * `Organization Administrator` or `Folder Creator` & `Project Creator` roles at the Organization/Folder level (for the `project-factory` module). - * `Billing Account User` role on the Billing Account. - * Once the project is created, subsequent applies for resources within the project will generally require `Owner` or `Editor` on the provisioned project. - * Alternatively, use a Service Account with appropriate permissions. -4. **Registered Domain Name:** You will need a domain name that you can manage DNS records for. This project will create a Cloud DNS Managed Zone for this domain (or a subdomain). -5. **(Optional) FleetDM License Key:** If you have a premium FleetDM license, you can provide it. - -## Configuration - -1. **Clone the repository (if applicable) or ensure you have all the files.** -2. **Create a `terraform.tfvars` file:** - Copy `terraform.tfvars.example` (you'll need to create this, see example below) to `terraform.tfvars` and populate it with your specific values. - - **Example `terraform.tfvars.example`:** - ```hcl - # Required: GCP Organization and Billing - org_id = "YOUR_ORGANIZATION_ID" # e.g., "123456789012" - billing_account_id = "YOUR_BILLING_ACCOUNT_ID" # e.g., "012345-6789AB-CDEF01" - - # Required: DNS Configuration - # The public DNS zone name (e.g., "example.com." - note the trailing dot) - # This project will create/manage this zone in Cloud DNS. - dns_zone_name = "your-domain.com." - # The fully qualified domain name for your Fleet instance (e.g., "fleet.your-domain.com.") - dns_record_name = "fleet.your-domain.com." - - # Optional: Project Configuration - project_name = "my-fleet-project" # Name for the new GCP project - random_project_id = true # Set to false if you want to use project_name as project_id (must be globally unique) - - # Optional: Labels - labels = { - application = "fleetdm" - environment = "production" - owner = "devops-team" - } - - # --- byo-project module specific variables (passed through from root) --- - # You can override defaults from byo-project/variables.tf here if needed, - # or set values for variables that don't have defaults. - - # Example: FleetDM License Key (if you have one) - # fleet_config = { - # license_key = "YOUR_FLEET_LICENSE_KEY" - # # You can also override other fleet_config defaults here: - # # image_tag = "fleetdm/fleet:latest" - # # installers_bucket_prefix = "my-fleet-installers" - # # fleet_cpu = "1000m" - # # fleet_memory = "4096Mi" - # # debug_logging = false - # # min_instance_count = 1 - # # max_instance_count = 3 - # } - - ``` - - **Key Variables to Set:** - * `org_id`: Your GCP Organization ID. - * `billing_account_id`: Your GCP Billing Account ID. - * `dns_zone_name`: The DNS zone that will be created/managed in Cloud DNS (e.g., `mydomain.com.`). **Must end with a dot.** - * `dns_record_name`: The specific DNS record for Fleet (e.g., `fleet.mydomain.com.`). **Must end with a dot.** - * `project_name`: A descriptive name for the project to be created. - * `fleet_config.license_key` (Optional, inside the `fleet_config` object): Your FleetDM license key if you have one. - - Review `variables.tf` and `byo-project/variables.tf` for all available options and their defaults. - -## Deployment Steps - -1. **Authenticate with GCP:** - Ensure your `gcloud` CLI is authenticated with the account having the necessary permissions. - ```bash - gcloud auth application-default login - ``` - If you need to specify a project for ADC (though project factory creates one): - ```bash - gcloud config set project YOUR_QUOTA_PROJECT_ID # A project where gcloud can store credentials - gcloud auth application-default set-quota-project YOUR_QUOTA_PROJECT_ID - ``` - -2. **Initialize Terraform:** - Navigate to the root directory of the project (where `main.tf` is located) and run: - ```bash - terraform init - ``` - -3. **Plan the Deployment:** - Review the changes Terraform will make: - ```bash - terraform plan -out=tfplan - ``` - -4. **Apply the Configuration:** - Provision the resources: - ```bash - terraform apply tfplan - ``` - This process can take a significant amount of time (15-30 minutes or more), especially for the initial project creation, API enablement, and Cloud SQL instance provisioning. - -5. **Run Database Migrations:** - After `terraform apply` completes, the Fleet application is deployed but the database schema needs to be initialized/migrated. The project creates a Cloud Run Job (`fleet-migration-job`) for this purpose. You need to execute this job manually. - - * **Find the Job Name and Location:** - The job name will be like `fleet-migration-job` (if prefix is `fleet`) or `-migration-job` in the region you deployed to (default `us-central1`). The project ID will be the one created by project factory. You can find the exact name and project ID in the Terraform output or by inspecting resources in the GCP console. - - * **Execute the Job using `gcloud`:** - Replace `[JOB_NAME]`, `[REGION]`, and `[PROJECT_ID]` with your actual values. - ```bash - PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1) # Adjust filter if your project_name is different - JOB_NAME="fleet-migration-job" # Or as configured by 'prefix' variable - REGION="us-central1" # Or as configured by 'region' variable - - gcloud run jobs execute ${JOB_NAME} \ - --region ${REGION} \ - --project ${PROJECT_ID} \ - --wait # Use --wait to see the job complete - ``` - If you don't have `terraform output -raw project_id` (because it's not defined in `outputs.tf`), you'll need to find the project ID manually, e.g., from the GCP console or by listing projects with `gcloud projects list`. - - * **Verify Job Completion:** - You can monitor the job's progress in the GCP Console under Cloud Run > Jobs. Ensure it completes successfully. - -6. **Update DNS (if not using Cloud DNS for the parent zone):** - If `dns_zone_name` (e.g., `fleet.your-domain.com.`) is a new zone created by this Terraform, and your parent domain (e.g., `your-domain.com.`) is managed elsewhere (e.g., GoDaddy, Cloudflare), you need to delegate this new zone. - * Get the Name Servers (NS) records for the newly created `google_dns_managed_zone` (`${var.prefix}-zone`): - ```bash - # First, get the project ID and zone name if you don't know them - PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1) - DNS_ZONE_INTERNAL_NAME="fleet-zone" # Or as configured by 'prefix' variable - - gcloud dns managed-zones describe ${DNS_ZONE_INTERNAL_NAME} \ - --project=${PROJECT_ID} \ - --format="value(nameServers)" - ``` - * Add these NS records to your parent domain's DNS settings at your domain registrar or DNS provider. - - If `dns_zone_name` (e.g., `your-domain.com.`) was an *existing* zone you own and manage via Cloud DNS in this project, Terraform would add records to it directly. The current setup *creates* the zone, so delegation is key if the parent is external. - -## Accessing FleetDM - -Once the deployment is complete, database migrations have run successfully, and DNS has propagated (which can take a few minutes): - -* Open your web browser and navigate to the `dns_record_name` you configured (e.g., `https://fleet.your-domain.com`). -* You should be greeted by the FleetDM setup page or login screen. - -## Key Resources Created (within the `byo-project` module) - -* **VPC Network (`fleet-network`):** Custom network for Fleet resources. -* **Subnet (`fleet-subnet`):** Subnetwork within the VPC. -* **Cloud Router & NAT (`fleet-cloud-router`, `fleet-vpc-nat`):** For outbound internet access from Cloud Run and other private resources. -* **Cloud SQL for MySQL (`fleet-mysql-xxxx`):** Managed MySQL database. -* **Memorystore for Redis (`fleet-cache`):** Managed Redis instance. -* **GCS Bucket (`fleet-installers-xxxx`):** For S3-compatible storage. -* **Secret Manager Secrets:** - * `fleet-db-password-xxxx`: Stores the generated MySQL user password. - * `fleet-private-key-xxxx`: Stores a generated private key for Fleet server. -* **Service Account (`fleet-run-sa`):** Used by Cloud Run services/jobs with necessary permissions. -* **Cloud Run Service (`fleet-service`):** Hosts the main Fleet application. -* **Cloud Run Job (`fleet-migration-job`):** For database migrations. -* **Network Endpoint Group (`fleet-neg`):** Connects the Load Balancer to the Cloud Run service. -* **Cloud DNS Managed Zone (`fleet-zone`):** Manages DNS records for `dns_zone_name`. -* **External HTTP(S) Load Balancer (`fleet-lb`):** Provides public access to Fleet via HTTPS. -* **DNS A Record:** Points `dns_record_name` to the Load Balancer's IP. - -(Note: `xxxx` indicates a random suffix added by some modules/resources for uniqueness.) - -## Cleaning Up - -To destroy all resources created by this Terraform project: -1. Navigate to the root directory of the project. -2. Run the destroy command: - ```bash - terraform destroy - ``` -3. Confirm by typing `yes` when prompted. - -## Important Considerations - -* **Costs:** Running these resources on GCP will incur costs. Be sure to understand the pricing for Cloud SQL, Memorystore, Cloud Run, Load Balancing, GCS, etc. Shut down resources with `terraform destroy` if they are no longer needed. -* **Permissions:** The user or service account running Terraform needs extensive permissions, especially for project creation. For ongoing management within the project, a role like `Project Editor` or more granular roles might suffice. -* **FleetDM Configuration:** This Terraform setup provisions the infrastructure. Further FleetDM application configuration (e.g., SSO, SMTP, agent options) will need to be done through the Fleet UI or API after deployment. -* **Security:** - * Review IAM permissions granted. - * The Load Balancer is configured for HTTPS and redirects HTTP. - * Cloud SQL and Memorystore are not publicly accessible and are connected via Private Service Access. - * Consider enabling IAP (Identity-Aware Proxy) on the Load Balancer for an additional layer of access control if needed (disabled by default in `loadbalancer.tf`). -* **Scalability:** Cloud Run scaling parameters (`min_instance_count`, `max_instance_count`) and database/cache tier can be adjusted in `variables.tf` (via `fleet_config`, `database_config`, `cache_config`) to suit your load. - - -### Networking Diagram -```mermaid -graph TD - subgraph External - Internet[(Internet)] - Users[Web Console / fleetd agent] - GitHub[(GitHub - Vulnerability Resources)] - end - - subgraph "Google Cloud Platform (GCP)" - subgraph VPC [VPC] - direction LR - subgraph PublicFacing [Public Zone] - LB[Global Load Balancer] - end - subgraph PrivateZone [Private Zone] - CloudRun[Cloud Run: Fleet App] - CloudSQL[Cloud SQL for MySQL] - Redis[Memorystore for Redis] - NAT[Cloud NAT] - end - - CloudRun --> Redis - CloudRun --> CloudSQL - CloudRun --> NAT - end - end - - Users -- "fleet.yourdomain.com" --> Internet - Internet -- "fleet.yourdomain.com" --> LB - LB --> CloudRun - NAT --> GitHub - -``` \ No newline at end of file