tools/mongodb-hybrid-dlp/main.tf (303 lines of code) (raw):

# Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. terraform { required_version = ">= 1.7.4" required_providers { google = { source = "hashicorp/google" version = ">= 6.11.2, < 7.0.0" } google-beta = { source = "hashicorp/google-beta" version = ">= 6.11.2, < 7.0.0" } mongodbatlas = { source = "mongodb/mongodbatlas" version = ">= 1.22.0" } random = { source = "hashicorp/random" version = ">= 3.6.3" } } } # When using ADC credentials, you'll need the following configuration for DLP API provider "google" { user_project_override = true billing_project = var.project_id } module "project" { source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/project?ref=daily-2024.11.15" name = var.project_id project_create = false services = [ "compute.googleapis.com", "dlp.googleapis.com", "storage.googleapis.com", "cloudscheduler.googleapis.com", ] } provider "mongodbatlas" { public_key = var.mongodbatlas_public_key private_key = var.mongodbatlas_private_key } module "vpc" { source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/net-vpc?ref=daily-2024.11.15" project_id = var.vpc_config.network_project != null ? var.vpc_config.network_project : module.project.project_id name = var.vpc_config.network subnets = [ { ip_cidr_range = var.vpc_config.subnet_cidr name = var.vpc_config.subnetwork region = var.region iam = {} } ] vpc_create = var.vpc_config.create } ### MongoDB related resources resource "mongodbatlas_advanced_cluster" "mongodb-cluster" { project_id = var.mongodbatlas_project_id name = "dlp-mongos" cluster_type = "REPLICASET" replication_specs { region_configs { electable_specs { instance_size = var.mongodbatlas_instance_type } provider_name = var.mongodbatlas_instance_type == "M0" ? "TENANT" : "GCP" backing_provider_name = var.mongodbatlas_instance_type == "M0" ? "GCP" : null priority = 7 region_name = var.mongodbatlas_region } } depends_on = [mongodbatlas_privatelink_endpoint_service.service.0] } resource "random_string" "password" { length = 24 lower = true upper = false numeric = true special = false } resource "mongodbatlas_custom_db_role" "role" { project_id = var.mongodbatlas_project_id role_name = "dlpChangeStreams" actions { action = "CHANGE_STREAM" resources { collection_name = "" database_name = "anyDatabase" } } actions { action = "FIND" resources { collection_name = "" database_name = "anyDatabase" } } } resource "mongodbatlas_database_user" "user" { username = "dlp-mongos" password = random_string.password.result project_id = var.mongodbatlas_project_id auth_database_name = "admin" roles { role_name = mongodbatlas_custom_db_role.role.role_name database_name = "admin" } roles { role_name = "readAnyDatabase" database_name = "admin" } scopes { name = mongodbatlas_advanced_cluster.mongodb-cluster.name type = "CLUSTER" } } # If using public instance, allow access from anywhere resource "mongodbatlas_project_ip_access_list" "access-list" { count = var.mongodbatlas_instance_type == "M0" ? 1 : 0 project_id = var.mongodbatlas_project_id cidr_block = "0.0.0.0/0" comment = "CIDR block for M0" } resource "google_compute_address" "endpoint-address" { for_each = var.mongodbatlas_instance_type != "M0" ? toset(mongodbatlas_privatelink_endpoint.endpoint.0.service_attachment_names) : toset([]) project = var.vpc_config.network_project != null ? var.vpc_config.network_project : module.project.project_id name = format("dlp-mongos-psc-%d", index(mongodbatlas_privatelink_endpoint.endpoint.0.service_attachment_names, each.key)) subnetwork = module.vpc.subnet_ids[format("%s/%s", var.region, var.vpc_config.subnetwork)] address_type = "INTERNAL" region = var.region } resource "google_compute_forwarding_rule" "forwarding-rule" { for_each = var.mongodbatlas_instance_type != "M0" ? toset(mongodbatlas_privatelink_endpoint.endpoint.0.service_attachment_names) : toset([]) target = each.value project = google_compute_address.endpoint-address[each.value].project region = google_compute_address.endpoint-address[each.value].region name = google_compute_address.endpoint-address[each.value].name ip_address = google_compute_address.endpoint-address[each.value].id network = module.vpc.network.name load_balancing_scheme = "" } resource "mongodbatlas_privatelink_endpoint_service" "service" { count = var.mongodbatlas_instance_type != "M0" ? 0 : 1 project_id = mongodbatlas_privatelink_endpoint.endpoint.0.project_id private_link_id = mongodbatlas_privatelink_endpoint.endpoint.0.private_link_id provider_name = "GCP" endpoint_service_id = module.vpc.network.name gcp_project_id = module.project.project_id dynamic "endpoints" { for_each = google_compute_address.endpoint-address content { ip_address = endpoints.value["address"] endpoint_name = google_compute_forwarding_rule.forwarding-rule[endpoints.key].name } } } resource "mongodbatlas_privatelink_endpoint" "endpoint" { count = var.mongodbatlas_instance_type != "M0" ? 0 : 1 project_id = var.mongodbatlas_project_id provider_name = "GCP" region = var.region } ### Sensitive Data Protection (aka DLP) resources # Pub/Sub topic for findings module "pubsub" { source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/pubsub?ref=daily-2024.11.15" project_id = module.project.project_id name = "dlp-mongos-findings" iam = {} } resource "google_data_loss_prevention_inspect_template" "template" { parent = format("projects/%s", module.project.project_id) description = "Inspect MongoDB contents" display_name = "MongoDB inspection" inspect_config { info_types { name = "EMAIL_ADDRESS" } info_types { name = "PERSON_NAME" } info_types { name = "LAST_NAME" } info_types { name = "PHONE_NUMBER" } info_types { name = "FIRST_NAME" } # For full reference of InfoTypes available, see: https://cloud.google.com/sensitive-data-protection/docs/infotypes-reference info_types { name = "FINLAND_NATIONAL_ID_NUMBER" } } } resource "google_data_loss_prevention_job_trigger" "trigger" { parent = format("projects/%s", module.project.project_id) triggers { manual {} } inspect_job { // Despite it saying "template name", it really expects a full path (eg. full ID) inspect_template_name = google_data_loss_prevention_inspect_template.template.id actions { pub_sub { topic = module.pubsub.id } # Unquote this and quote above to send findings to Cloud Security Command Center # publish_summary_to_cscc { # } } storage_config { hybrid_options { description = "Hybrid job trigger for MongoDB" required_finding_label_keys = [] labels = {} } } } } ### Supporting resources module "service-account" { source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/iam-service-account?ref=daily-2024.11.15" project_id = module.project.project_id name = "dlp-mongos" iam = {} iam_project_roles = { "${module.project.project_id}" = [ "roles/logging.logWriter", "roles/monitoring.metricWriter", "roles/dlp.jobTriggersEditor", "roles/dlp.jobsEditor", "roles/serviceusage.serviceUsageConsumer" ] } } resource "random_string" "bucket-suffix" { length = 8 lower = true upper = false numeric = true special = false } # Bucket for storing the state file module "state-bucket" { source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/gcs?ref=daily-2024.11.15" project_id = module.project.project_id prefix = null name = format("dlp-mongos-%s", random_string.bucket-suffix.result) location = var.region versioning = false labels = {} iam = { "roles/storage.objectAdmin" = [module.service-account.iam_email] } } ### Cloud Function module "function" { source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/cloud-function-v2?ref=daily-2024.11.15" project_id = module.project.project_id region = var.region name = "dlp-mongos" service_account = module.service-account.email bucket_name = format("dlp-mongos-cf-%s", random_string.bucket-suffix.result) bucket_config = { force_destroy = true location = var.region } bundle_config = { path = "${path.module}" } function_config = { entry_point = "DLPFunctionHTTP", runtime = "go122", timeout_seconds = 3600, # 1h timeout, if you increase the function run time, make sure it's under 1 hour as some time is required for housekeeping instance_count = 1, cpu = 1, memory_mb = 256, } iam = { "roles/run.invoker" = [module.service-account.iam_email] } environment_variables = { MONGO_CONNECTION_STRING = var.mongodbatlas_instance_type == "M0" ? mongodbatlas_advanced_cluster.mongodb-cluster.connection_strings[0].standard_srv : mongodbatlas_advanced_cluster.mongodb-cluster.connection_strings[0].private_srv, MONGO_USERNAME = mongodbatlas_database_user.user.username, MONGO_PASSWORD = mongodbatlas_database_user.user.password, MONGO_DEPLOYMENTS = join(",", var.mongodb_deployments), MONGO_DATABASES = join(",", var.mongodb_databases) MONGO_COLLECTIONS = join(",", var.mongodb_collections), DLP_TRIGGER_NAME = google_data_loss_prevention_job_trigger.trigger.id, PROJECT_ID = module.project.project_id, STATE_FILE = format("gs://%s/state.json", module.state-bucket.name) RUN_PERIOD = format("%ds", (var.run_period * 60) - 30) # Leave some time to save things } } # Run the function on a regular schedule resource "google_cloud_scheduler_job" "job" { project = module.project.project_id region = var.scheduler_region == null ? var.region : var.scheduler_region name = "dlp-mongos-job" description = "Run DLP function regularly" schedule = format("*/%d * * * *", var.run_period) time_zone = "Europe/Amsterdam" attempt_deadline = "1200s" retry_config { retry_count = 1 } http_target { http_method = "GET" uri = module.function.uri oidc_token { service_account_email = module.service-account.email } } }