diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl index f4296fd7..cbd39906 100644 --- a/terraform/.terraform.lock.hcl +++ b/terraform/.terraform.lock.hcl @@ -20,3 +20,22 @@ provider "registry.terraform.io/hashicorp/google" { "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", ] } + +provider "registry.terraform.io/hashicorp/random" { + version = "3.6.3" + hashes = [ + "h1:In4XBRMdhY89yUoTUyar3wDF28RJlDpQzdjahp59FAk=", + "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451", + "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8", + "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe", + "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1", + "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36", + "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30", + "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615", + "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad", + "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556", + "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0", + ] +} diff --git a/terraform/main.tf b/terraform/main.tf index 9f42a525..dd276d93 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -7,70 +7,63 @@ terraform { source = "hashicorp/google" version = "6.8.0" } + random = { + source = "hashicorp/random" + version = "3.6.3" + } } } +# Configure the Google Cloud provider provider "google" { project = var.project region = var.region zone = var.zone } -locals { - institutions = [ - { - name = "Example College" - id = "example-college" - }, - { - name = "Example University" - id = "example-university" - } - ] -} +module "network" { + source = "./modules/network" -resource "google_storage_bucket" "upload_buckets" { - for_each = { for inst in local.institutions : inst.id => inst } + environment = var.environment + region = var.region +} - name = each.value.id - location = "US" +module "iam" { + source = "./modules/iam" - cors { - origin = ["*"] - method = ["POST", "PUT"] - response_header = [ - "Content-Type", - "Access-Control-Allow-Origin", - "X-Goog-Content-Length-Range" - ] - max_age_seconds = 3600 - } + environment = var.environment } -resource "google_service_account" "webapp_service_acccount" { - account_id = "webapp" - display_name = "Webapp Service Account" - description = "Service account for the webapp" -} +module "database" { + source = "./modules/database" -resource "google_project_iam_member" "webapp_service_acccount" { - project = var.project - role = "roles/storage.objectUser" - member = "serviceAccount:${google_service_account.webapp_service_acccount.email}" + environment = var.environment + region = var.region + zone = var.zone + database_name = var.database_name + database_version = var.database_version + + cloud_run_service_account_email = module.iam.cloud_run_service_account_email + network_id = module.network.network_id } -resource "google_project_iam_member" "token_creator" { - project = var.project - role = "roles/iam.serviceAccountTokenCreator" - member = "serviceAccount:${google_service_account.webapp_service_acccount.email}" +locals { + image = "us-central1-docker.pkg.dev/dev-sst-439514/cloud-run-source-deploy/student-success-tool/central-dev-app-deploy@sha256:945d7952e047bf299875d35981aa9dccd955fc315d68906bff522ad2899cefe1" } -resource "google_storage_bucket" "default" { - name = "sst-terraform-state" - force_destroy = true - location = "US" - storage_class = "STANDARD" - versioning { - enabled = true - } +module "service" { + source = "./modules/service" + + project = var.project + environment = var.environment + region = var.region + image = local.image + database_name = var.database_name + + database_password_secret_id = module.database.password_secret_id + database_instance_connection_name = module.database.instance_connection_name + database_instance_private_ip = module.database.instance_private_ip + network_id = module.network.network_id + subnetwork_id = module.network.subnetwork_id + cloud_run_service_account_email = module.iam.cloud_run_service_account_email } diff --git a/terraform/modules/database/main.tf b/terraform/modules/database/main.tf new file mode 100644 index 00000000..46328fa5 --- /dev/null +++ b/terraform/modules/database/main.tf @@ -0,0 +1,131 @@ +resource "google_project_service" "services" { + for_each = toset(var.required_services) + + service = each.value + disable_on_destroy = false +} + +resource "random_password" "db_password" { + length = 16 + special = true +} + +resource "google_secret_manager_secret" "db_password_secret" { + secret_id = "${var.environment}-db-password" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "db_password_secret_version" { + secret = google_secret_manager_secret.db_password_secret.id + secret_data = random_password.db_password.result +} + +data "google_secret_manager_secret_version" "db_password_secret_version" { + secret = google_secret_manager_secret.db_password_secret.id + version = "latest" +} + +resource "google_sql_database_instance" "db_instance" { + deletion_protection = false + name = "${var.environment}-db-instance" + database_version = var.database_version + region = var.region + settings { + tier = "db-f1-micro" + location_preference { + zone = var.zone + } + ip_configuration { + ipv4_enabled = false + private_network = var.network_id + ssl_mode = "ENCRYPTED_ONLY" + } + } + timeouts { + create = "1h" + } +} + +resource "google_sql_database" "db" { + name = var.database_name + instance = google_sql_database_instance.db_instance.name +} + +resource "google_sql_user" "db_user" { + name = "root" + instance = google_sql_database_instance.db_instance.name + password = data.google_secret_manager_secret_version.db_password_secret_version.secret_data +} + +resource "google_sql_ssl_cert" "db_ssl_cert" { + instance = google_sql_database_instance.db_instance.name + common_name = "${var.environment}-db-ssl-cert" +} + +resource "google_secret_manager_secret" "db_client_cert" { + secret_id = "${var.environment}-db-client-cert" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "db_client_cert_version" { + deletion_policy = "DELETE" + enabled = true + secret = google_secret_manager_secret.db_client_cert.id + secret_data = google_sql_ssl_cert.db_ssl_cert.cert +} + +resource "google_secret_manager_secret" "db_client_key" { + secret_id = "${var.environment}-db-client-key" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "db_client_key_version" { + deletion_policy = "DELETE" + enabled = true + secret = google_secret_manager_secret.db_client_key.id + secret_data = google_sql_ssl_cert.db_ssl_cert.private_key +} + +resource "google_secret_manager_secret" "db_server_ca" { + secret_id = "${var.environment}-db-server-ca" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "db_server_ca_version" { + deletion_policy = "DELETE" + enabled = true + secret = google_secret_manager_secret.db_server_ca.id + secret_data = google_sql_ssl_cert.db_ssl_cert.server_ca_cert +} + +resource "google_secret_manager_secret_iam_member" "cloudrun_sa_db_password_access" { + secret_id = google_secret_manager_secret.db_password_secret.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${var.cloud_run_service_account_email}" +} + +resource "google_secret_manager_secret_iam_member" "cloudrun_sa_db_client_cert_access" { + secret_id = google_secret_manager_secret.db_client_cert.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${var.cloud_run_service_account_email}" +} + +resource "google_secret_manager_secret_iam_member" "cloudrun_sa_db_client_key_access" { + secret_id = google_secret_manager_secret.db_client_key.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${var.cloud_run_service_account_email}" +} + +resource "google_secret_manager_secret_iam_member" "cloudrun_sa_db_server_ca_access" { + secret_id = google_secret_manager_secret.db_server_ca.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${var.cloud_run_service_account_email}" +} diff --git a/terraform/modules/database/outputs.tf b/terraform/modules/database/outputs.tf new file mode 100644 index 00000000..c1b504ac --- /dev/null +++ b/terraform/modules/database/outputs.tf @@ -0,0 +1,12 @@ +output "instance_connection_name" { + value = google_sql_database_instance.db_instance.connection_name +} + +output "password_secret_id" { + value = google_secret_manager_secret.db_password_secret.id + sensitive = true +} + +output "instance_private_ip" { + value = google_sql_database_instance.db_instance.private_ip_address +} diff --git a/terraform/modules/database/variables.tf b/terraform/modules/database/variables.tf new file mode 100644 index 00000000..8f6399b8 --- /dev/null +++ b/terraform/modules/database/variables.tf @@ -0,0 +1,35 @@ +variable "required_services" { + type = list(string) + default = [ + "sqladmin.googleapis.com", + "secretmanager.googleapis.com" + ] +} + +variable "environment" { + type = string +} + +variable "region" { + type = string +} + +variable "zone" { + type = string +} + +variable "database_version" { + type = string +} + +variable "database_name" { + type = string +} + +variable "network_id" { + type = string +} + +variable "cloud_run_service_account_email" { + type = string +} diff --git a/terraform/modules/iam/main.tf b/terraform/modules/iam/main.tf new file mode 100644 index 00000000..cc3710f5 --- /dev/null +++ b/terraform/modules/iam/main.tf @@ -0,0 +1,12 @@ +variable "environment" { + type = string +} + +resource "google_service_account" "cloudrun_sa" { + account_id = "${var.environment}-cloudrun-sa" + display_name = "${var.environment} Cloud Run Service Account" +} + +output "cloud_run_service_account_email" { + value = google_service_account.cloudrun_sa.email +} diff --git a/terraform/modules/network/main.tf b/terraform/modules/network/main.tf new file mode 100644 index 00000000..325190ac --- /dev/null +++ b/terraform/modules/network/main.tf @@ -0,0 +1,32 @@ +resource "google_project_service" "services" { + for_each = toset(var.required_services) + + service = each.value + disable_on_destroy = false +} + +resource "google_compute_network" "vpc_network" { + name = "${var.environment}-vpc-network" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "vpc_subnetwork" { + name = "${var.environment}-vpc-subnetwork" + network = google_compute_network.vpc_network.id + region = var.region + ip_cidr_range = "10.0.1.0/24" +} + +resource "google_compute_global_address" "vpc_connector_ip" { + name = "${var.environment}-vpc-connector-ip" + purpose = "VPC_PEERING" + address_type = "INTERNAL" + prefix_length = 16 + network = google_compute_network.vpc_network.id +} + +resource "google_service_networking_connection" "vpc_connection" { + network = google_compute_network.vpc_network.id + service = "servicenetworking.googleapis.com" + reserved_peering_ranges = [google_compute_global_address.vpc_connector_ip.name] +} diff --git a/terraform/modules/network/outputs.tf b/terraform/modules/network/outputs.tf new file mode 100644 index 00000000..104f4a4e --- /dev/null +++ b/terraform/modules/network/outputs.tf @@ -0,0 +1,7 @@ +output "network_id" { + value = google_compute_network.vpc_network.id +} + +output "subnetwork_id" { + value = google_compute_subnetwork.vpc_subnetwork.id +} diff --git a/terraform/modules/network/variables.tf b/terraform/modules/network/variables.tf new file mode 100644 index 00000000..f8139867 --- /dev/null +++ b/terraform/modules/network/variables.tf @@ -0,0 +1,14 @@ +variable "required_services" { + type = list(string) + default = [ + "compute.googleapis.com", + ] +} + +variable "region" { + type = string +} + +variable "environment" { + type = string +} diff --git a/terraform/modules/service/main.tf b/terraform/modules/service/main.tf new file mode 100644 index 00000000..456257d8 --- /dev/null +++ b/terraform/modules/service/main.tf @@ -0,0 +1,125 @@ +resource "google_project_service" "services" { + for_each = toset(var.required_services) + + service = each.value + disable_on_destroy = false +} + +resource "google_secret_manager_secret_iam_member" "cloudrun_sa_db_client_cert_access" { + secret_id = "projects/${var.project}/secrets/test-api-env-file" + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${var.cloud_run_service_account_email}" +} + +data "google_secret_manager_secret_version" "database_password_secret_version" { + secret = var.database_password_secret_id + version = "latest" +} + +resource "google_cloud_run_v2_service" "webapp" { + deletion_protection = false + location = var.region + name = "${var.environment}-webapp" + launch_stage = "GA" + template { + service_account = var.cloud_run_service_account_email + containers { + image = var.image + env { + name = "ENV_FILE_PATH" + value = "/vol_mt/env_vars/.env" + } + env { + name = "DB_USER" + value = "root" + } + env { + name = "DB_PASS" + value = data.google_secret_manager_secret_version.database_password_secret_version.secret_data + } + env { + name = "DB_NAME" + value = var.database_name + } + env { + name = "INSTANCE_HOST" + value = var.database_instance_private_ip + } + env { + name = "DB_PORT" + value = "3306" + } + volume_mounts { + mount_path = "/vol_mt/env_vars" + name = "env_file" + } + volume_mounts { + mount_path = "/vol_mt/certs/cert" + name = "${var.environment}-db-client-cert-vol" + } + volume_mounts { + mount_path = "/vol_mt/certs/key" + name = "${var.environment}-db-client-key-vol" + } + volume_mounts { + mount_path = "/vol_mt/certs/server" + name = "${var.environment}-db-server-ca-vol" + } + } + vpc_access { + network_interfaces { + network = var.network_id + subnetwork = var.subnetwork_id + } + egress = "ALL_TRAFFIC" + } + volumes { + name = "cloudsql" + cloud_sql_instance { + instances = [var.database_instance_connection_name] + } + } + volumes { + name = "env_file" + secret { + items { + path = ".env" + version = "latest" + } + + secret = "${var.environment}-api-env-file" + } + } + volumes { + name = "${var.environment}-db-client-cert-vol" + secret { + items { + path = "client-cert.pem" + version = "latest" + } + + secret = "${var.environment}-db-client-cert" + } + } + volumes { + name = "${var.environment}-db-client-key-vol" + secret { + items { + path = "client-key.pem" + version = "latest" + } + secret = "${var.environment}-db-client-key" + } + } + volumes { + name = "${var.environment}-db-server-ca-vol" + secret { + items { + path = "server-ca.pem" + version = "latest" + } + secret = "${var.environment}-db-server-ca" + } + } + } +} diff --git a/terraform/modules/service/variables.tf b/terraform/modules/service/variables.tf new file mode 100644 index 00000000..2dd5b8fd --- /dev/null +++ b/terraform/modules/service/variables.tf @@ -0,0 +1,53 @@ +variable "required_services" { + type = list(string) + default = [ + "iam.googleapis.com", + "run.googleapis.com", + "vpcaccess.googleapis.com", + ] +} + +variable "project" { + type = string +} + +variable "environment" { + type = string +} + +variable "region" { + type = string +} + +variable "image" { + type = string +} + +variable "database_instance_connection_name" { + type = string +} + +variable "database_instance_private_ip" { + type = string +} + +variable "database_name" { + type = string +} + +variable "database_password_secret_id" { + type = string + sensitive = true +} + +variable "network_id" { + type = string +} + +variable "subnetwork_id" { + type = string +} + +variable "cloud_run_service_account_email" { + type = string +} diff --git a/terraform/variables.tf b/terraform/variables.tf index 9ae7cd31..1713a492 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -1,9 +1,29 @@ -variable "project" { } +variable "project" { + description = "The project ID" + default = "dev-sst-439514" +} variable "region" { - default = "us-east4" + description = "The region to deploy resources" + default = "us-central1" +} + +variable "environment" { + description = "The environment to deploy resources" + default = "test" } variable "zone" { - default = "us-east4-c" + description = "The zone to deploy resources" + default = "us-central1-c" +} + +variable "database_version" { + description = "The database version" + default = "MYSQL_8_0" +} + +variable "database_name" { + description = "The database name" + default = "all_tables" }