ClamAV with Terraform
- First we created the projects on the GCP, road-e-learning folder with three projects:
e-learning-dev-416612 (https://console.cloud.google.com/welcome?project=e-learning-dev-416612)
e-learning-nightly (https://console.cloud.google.com/welcome?project=e-learning-nightly) e-learning-stable (https://console.cloud.google.com/welcome?project=e-learning-stable)
Here is the link from the GitHub repo when you have all the scripts and everything but i wrote it here in case we delete that repo and that project to have documentation of how we created ClamAV malware scanner on our own projects:
https://github.com/Attracs/terraform-road-e-learning/tree/nightly
2. After that we created an service account on the dev environment with gcloud commands:
gcloud config set project PROJECT_ID
gcloud iam service-accounts create SERVICE_ACCOUNT_NAME \
 --description="DESCRIPTION" \
--display-name="DISPLAY_NAME"
gcloud projects add-iam-policy-binding PROJECT_I D \
--member="serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.c om" \
 --role="ROLE_NAME"
3. Writing scripts in Terraform for creating the buckets (unscanned, quarantined, clean and add policy to the buckets) * create file named buckets.tf
resource "google_storage_bucket" "unscanned_bucket" {
name = "unscanned-${var.project}"
location = var.location
}
resource "google_storage_bucket" "quarantined_bucket" {
name = "quarantined-${var.project}"
location = var.location
}
resource "google_storage_bucket" "clean_bucket" {
name = "clean-${var.project}"
location = var.location
}
resource "google_storage_bucket" "cvd_mirror_bucket" {
name = "cvd-mirror-${var.project}"
location = var.location
}
resource "google_storage_bucket_iam_member" "unscanned_bucket_policy" {
bucket = google_storage_bucket.unscanned_bucket.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${var.SERVICE_ACCOUNT}"
}
resource "google_storage_bucket_iam_member" "quarantined_bucket_policy" {
bucket = google_storage_bucket.quarantined_bucket.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${var.SERVICE_ACCOUNT}"
}
resource "google_storage_bucket_iam_member" "clean_bucket_policy" {
bucket = google_storage_bucket.clean_bucket.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${var.SERVICE_ACCOUNT}"
}
resource "google_storage_bucket_iam_member" "cvd_mirror_bucket_policy" {
bucket = google_storage_bucket.cvd_mirror_bucket.name
role = "roles/storage.objectAdmin"
member = "serviceAccount:${var.SERVICE_ACCOUNT}"
}
resource "google_project_iam_member" "monitoring_metric_writer" {
project = var.project
role = "roles/monitoring.metricWriter"
member = "serviceAccount:${var.SERVICE_ACCOUNT}"
}
4. Create terraform file named api-enable.tf (all the google apis we need)
variable "gcp_service_list" {
description ="The list of apis necessary for the project"
type = list(string)
default = [
"cloudresourcemanager.googleapis.com",
"serviceusage.googleapis.com"
]
}
resource "google_project_service" "gcp_services" {
for_each = toset(var.gcp_service_list)
service = each.key
}
# Enable APIs
resource "google_project_service" "artifact_registry" {
service = "artifactregistry.googleapis.com"
}
resource "google_project_service" "cloud_run_admin" {
service = "run.googleapis.com"
}
resource "google_project_service" "eventarc" {
service = "eventarc.googleapis.com"
}
resource "google_project_service" "cloud_logging" {
service = "logging.googleapis.com"
}
resource "google_project_service" "cloud_build" {
service = "cloudbuild.googleapis.com"
}
resource "google_project_service" "cloud_scheduler" {
service = "cloudscheduler.googleapis.com"
}
resource "google_project_service" "pubsub" {
service = "pubsub.googleapis.com"
}
4. Create file named variables.tf (to define variables like service_account,region,zone,billing account)
# This is the service account we use to create buckets and iam policiy
variable "SERVICE_ACCOUNT" {
default = "terraform@e-learning-nightly.iam.gserviceaccount.com"
}
# This is the service account for creating CloudRun and Trigger
variable "SERVICE_ACCOUNT_virus" {
default = "malware-scanner@e-learning-nightly.iam.gserviceaccount.com"
}
variable "project" {
default = "e-learning-nightly"
}
variable "region" {
default = "europe-north1"
}
variable "zone" {
default = "europe-north1-a"
}
variable "billing_account" {
type = string
default = "01228C-D05D29-D4D6B0"
}
#attracs.net id: 440817507889
variable "org_id" {
type = string
default = "440817507889"
}
variable "location" {
type = string
default = "europe-north1"
}
5. Create file named main.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "4.60.1"
}
}
}
provider "google" {
# Configuration options
project = var.project
region = var.region
zone = var.zone
}
# Required for load balancer
provider "google-beta" {
# Configuration options
project = var.project
region = var.region
zone = var.zone
}
# Use cloud storage as backend, change PREFIX!
terraform {
backend "gcs" {
bucket = "adi-terraform-state"
prefix = "terraform/state/road-e-learning"
}
}
6. And lastly you create terraform.tfvars ( where you define tf variables )
region = "europe-north1"
location = "eu"
project = "e-learning-nightly"
7. After you create all this terraform files you need to log in into google cloud with
gcloud auth activate-service-account [ACCOUNT] --key-file=example.json
in the [ACCOUNT] you need to define account for example terraform@....com and in the json file is the key you should download from credentials on GCP.
8. After the succesful login we start with applying terraform scripts on gcp:
terraform init
terraform plan
terraform apply
9. After successful applying of the terraform we continue with automated bash srcipt for creating the Cloud Run (building the container and get URL) and Trigger (which is scanning files to detect if it has malware or not)
Everytime you need to change variables for your own project: (this is the example for e-learning-nightly):
#!/bin/bash
# Variables
REGION="europe-north1"
LOCATION="eu"
PROJECT_ID="e-learning-nightly"
SERVICE_NAME="malware-scanner"
#Here We need to create service account. Next Test >>
#SERVICE_ACCOUNT="sa-${SERVICE_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
SERVICE_ACCOUNT="malware-scanner@e-learning-nightly.iam.gserviceaccount.com"
# Clone GitHub repository
git clone https://github.com/GoogleCloudPlatform/docker-clamav-malware-scanner.git
# Change directory
cd docker-clamav-malware-scanner/cloudrun-malware-scanner
# Edit configuration file
sed "s/-bucket-name/-${PROJECT_ID}/" config.json.tmpl > config.json
# View updated configuration file
cat config.json
# Initial population of ClamAV malware database mirror
python3 -m venv pyenv
. pyenv/bin/activate
pip3 install crcmod cvdupdate
./updateCvdMirror.sh "cvd-mirror-e-learning-nightly"
deactivate
# Check contents of the mirror bucket
gsutil ls "gs://cvd-mirror-${PROJECT_ID}/cvds"
# Deploy Cloud Run service CHECK FOR REAM CONFIGS AND PERFOMANSE
gcloud beta run deploy "${SERVICE_NAME}" \
--source . \
--region "${REGION}" \
--no-allow-unauthenticated \
--memory 4Gi \
--cpu 1 \
--concurrency 20 \
--min-instances 1 \
--max-instances 2 \
--no-cpu-throttling \
--cpu-boost \
--project="${PROJECT_ID}" \
--service-account="${SERVICE_ACCOUNT}"
10. When this is complited you will get SERVICE URL in the Cloud Run which you need to define as variable in the seccond part of the bash script (here is the example):
# Store Service URL value
#SERVICE_URL="SERVICE_URL"
# This is the example for e-learning-nightly you need to change for your own url that you get from CloudRun
SERVICE_URL="https://malware-scanner-45pu7cqqiq-lz.a.run.app"
# Check running service and ClamAV version
curl -D - -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
${SERVICE_URL}
# Add IAM permissions
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")
PUBSUB_SERVICE_ACCOUNT="service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member="serviceAccount:${PUBSUB_SERVICE_ACCOUNT}" \
--role='roles/iam.serviceAccountTokenCreator'
STORAGE_SERVICE_ACCOUNT=$(gsutil kms serviceaccount -p "${PROJECT_ID}")
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--member "serviceAccount:${STORAGE_SERVICE_ACCOUNT}" \
--role "roles/pubsub.publisher"
gcloud run services add-iam-policy-binding "${SERVICE_NAME}" \
--region="${REGION}" \
--member "serviceAccount:${SERVICE_ACCOUNT}" \
--role roles/run.invoker
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--member "serviceAccount:${SERVICE_ACCOUNT}" \
--role "roles/eventarc.eventReceiver"
# Create Eventarc trigger
BUCKET_NAME="unscanned-${PROJECT_ID}"
gcloud eventarc triggers create "trigger-${BUCKET_NAME}-${SERVICE_NAME}" \
--destination-run-service="${SERVICE_NAME}" \
--destination-run-region="${REGION}" \
--location="${LOCATION}" \
--event-filters="type=google.cloud.storage.object.v1.finalized" \
--event-filters="bucket=${BUCKET_NAME}" \
--service-account="${SERVICE_ACCOUNT}"
# Update Pub/Sub subscription acknowledgement deadline
SUBSCRIPTION_NAME=$(gcloud eventarc triggers describe \
"trigger-${BUCKET_NAME}-${SERVICE_NAME}" \
--location="${LOCATION}" \
--format="get(transport.pubsub.subscription)")
gcloud pubsub subscriptions update "${SUBSCRIPTION_NAME}" --ack-deadline=120
# Create Cloud Scheduler job
while : ; do
MINUTE="$((RANDOM%55 + 3))"
[[ $((MINUTE % 10)) != 0 ]] && break
done
gcloud scheduler jobs create http \
"${SERVICE_NAME}-mirror-update" \
--location="europe-west3" \
--schedule="${MINUTE} */2 * * *" \
--oidc-service-account-email="${SERVICE_ACCOUNT}" \
--uri="${SERVICE_URL}" \
--http-method=post \
--message-body='{"kind":"schedule#cvd_update"}' \
--headers="Content-Type=application/json"
# Test pipeline by uploading files
# Replace FILENAME with the name of the clean text file
gsutil cp FILENAME "gs://unscanned-${PROJECT_ID}"
# Check clean bucket
gsutil ls -r "gs://clean-${PROJECT_ID}"
# Check unscanned bucket
gsutil ls -r "gs://unscanned-${PROJECT_ID}"
# Upload infected file
echo -e 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' \
| gsutil cp - "gs://unscanned-${PROJECT_ID}/eicar-infected.txt"
# Check quarantined bucket
gsutil ls -r "gs://quarantined-${PROJECT_ID}"
# Check unscanned bucket after uploading infected file
gsutil ls -r "gs://unscanned-${PROJECT_ID}"
11. After you successfully completed the terraform and bash scripts you have functional ClamAV installed on you project with Buckets, CloudRun and Trigger.
5.