terraform/service/main.tf (129 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.
provider "google" {
project = var.project_id
region = var.region
}
data "google_project" "project" {
project_id = var.project_id
}
## Read config and extract bucket names
locals {
config_json = jsondecode(var.config_json)
unscanned_bucket_names = local.config_json.buckets[*].unscanned
}
## Verify service account exists
#
data "google_service_account" "malware_scanner_sa" {
account_id = var.service_name
project = data.google_project.project.project_id
}
## Lookup the hash of the latest image
##
data "google_artifact_registry_docker_image" "scanner-service-image" {
location = var.region
repository_id = var.service_name
image_name = "${var.service_name}:latest"
project = data.google_project.project.project_id
}
## Deploy the Cloud Run Service
#
resource "google_cloud_run_v2_service" "malware_scanner" {
name = var.service_name
location = var.region
ingress = "INGRESS_TRAFFIC_ALL"
template {
scaling {
max_instance_count = 5
min_instance_count = 1
}
service_account = data.google_service_account.malware_scanner_sa.email
timeout = "${var.deadline}s"
max_instance_request_concurrency = 20
containers {
image = data.google_artifact_registry_docker_image.scanner-service-image.self_link
resources {
limits = {
cpu = "1"
memory = "4Gi"
}
cpu_idle = false # CPU is still allocated outside of requests
startup_cpu_boost = true
}
env {
name = "CONFIG_JSON"
value = var.config_json
}
startup_probe {
# Allow 90 secs before we start probing
# Then allow up to 15*10 = 150s before we give up
# (total possible startup time = 240s)
initial_delay_seconds = 90
failure_threshold = 15
period_seconds = 10
timeout_seconds = 1
http_get {
path = "/ready"
}
}
liveness_probe {
# Poll every 30 secs, allowing for 3 failures
#
period_seconds = 30
failure_threshold = 3
timeout_seconds = 30
http_get {
path = "/ready"
}
}
}
}
traffic {
type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
percent = 100
}
}
## Verify unscanned bucket(s) exist
#
data "google_storage_bucket" "unscanned-bucket" {
for_each = toset(local.unscanned_bucket_names)
name = each.value
}
## Create EventArc Triggers on unscanned bucket(s)
#
resource "google_eventarc_trigger" "gcs-object-written" {
for_each = toset(local.unscanned_bucket_names)
name = "gcs-trigger-${each.value}"
location = var.bucket_location
matching_criteria {
attribute = "type"
value = "google.cloud.storage.object.v1.finalized"
}
matching_criteria {
attribute = "bucket"
value = data.google_storage_bucket.unscanned-bucket[each.value].name
}
destination {
cloud_run_service {
service = google_cloud_run_v2_service.malware_scanner.name
region = google_cloud_run_v2_service.malware_scanner.location
}
}
service_account = data.google_service_account.malware_scanner_sa.email
}
## Update pubsub subscriptions to increase deadlines
#
resource "null_resource" "update-subscription-ack-deadline" {
for_each = toset(local.unscanned_bucket_names)
provisioner "local-exec" {
command = "gcloud pubsub subscriptions update \"${google_eventarc_trigger.gcs-object-written[each.key].transport[0].pubsub[0].subscription}\" --ack-deadline=${var.deadline}"
}
}
## Deploy scheduled task to refresh the CVD Mirror
# To avoid having too many clients use the same time slot,
# ClamAV requires that updates are scheduled at a random minute between 3
# and 57 avoiding multiples of 10.
resource "random_integer" "cvd_mirror_update_schedule_minutes" {
min = 3
max = 57
}
locals {
# Avoid multiples of 10 by subtracting 3.
cvd_mirror_update_schedule_minutes = (
random_integer.cvd_mirror_update_schedule_minutes.result % 10 == 0
? random_integer.cvd_mirror_update_schedule_minutes.result - 3
: random_integer.cvd_mirror_update_schedule_minutes.result
)
}
resource "google_cloud_scheduler_job" "cvd_mirror_update" {
name = "${var.service_name}-cvd-mirror-update"
schedule = "${local.cvd_mirror_update_schedule_minutes} */2 * * *"
attempt_deadline = "320s"
region = var.region
http_target {
http_method = "POST"
uri = google_cloud_run_v2_service.malware_scanner.uri
body = base64encode("{\"kind\":\"schedule#cvd_update\"}")
headers = {
"Content-Type" = "application/json"
}
oidc_token {
service_account_email = data.google_service_account.malware_scanner_sa.email
}
}
}