为上传到 Cloud Storage 的文件部署自动恶意软件扫描

Last reviewed 2023-06-20 UTC

本文档介绍如何部署对上传到 Cloud Storage 的文件进行自动恶意软件扫描中的架构。

本部署指南假定您熟悉以下技术的基本功能:

架构

下图显示了您在本文档中创建的部署架构:

恶意软件扫描流水线的架构。

该图显示了由此架构管理的以下两个流水线:

  • 文件扫描流水线,用于检查上传的文件是否包含恶意软件。
  • ClamAV 恶意软件数据库镜像更新流水线,用于维护 ClamAV 使用的恶意软件数据库的最新镜像。

如需详细了解此架构,请参阅对上传到 Cloud Storage 的文件进行自动恶意软件扫描

目标

  • 在 Cloud Storage 存储桶中构建 ClamAV 恶意软件定义数据库的镜像。

  • 构建具有以下功能的 Cloud Run 服务:

    • 使用 ClamAV 扫描 Cloud Storage 存储桶中的文件是否包含恶意软件,并根据扫描结果将已扫描的文件移到干净的存储桶或隔离的存储桶。
    • 在 Cloud Storage 中维护 ClamAV 恶意软件定义数据库的镜像。
  • 创建 Eventarc 触发器,以在有文件上传到 Cloud Storage 时触发恶意软件扫描服务。

  • 创建 Cloud Scheduler 作业,以触发恶意软件扫描服务来刷新 Cloud Storage 中的恶意软件定义数据库的镜像。

费用

此架构使用 Google Cloud 的以下收费组件:

如需根据您的预计使用量来估算费用,请使用价格计算器

准备工作

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  3. 确保您的 Google Cloud 项目已启用结算功能

  4. 启用 Artifact Registry, Cloud Run, Eventarc, Logging, Cloud Scheduler, Pub/Sub, and Cloud Build API。

    启用 API

  5. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  6. 确保您的 Google Cloud 项目已启用结算功能

  7. 启用 Artifact Registry, Cloud Run, Eventarc, Logging, Cloud Scheduler, Pub/Sub, and Cloud Build API。

    启用 API

  8. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Google Cloud 控制台的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Google Cloud CLI 且已为当前项目设置值的 Shell 环境。该会话可能需要几秒钟时间来完成初始化。

  9. 在此部署中,您将在 Cloud Shell 中运行所有命令。

设置您的环境

在本部分中,您需要为整个部署使用的值(例如区域和地区)分配设置。在此部署中,您将使用 us-central1 作为 Cloud Run 服务的区域,使用 us 作为 Eventarc 触发器和 Cloud Storage 存储桶的位置。

  1. 在 Cloud Shell 中,设置常见的 shell 变量,包括区域和位置:

    REGION=us-central1
    LOCATION=us
    PROJECT_ID=PROJECT_ID
    SERVICE_NAME="malware-scanner"
    SERVICE_ACCOUNT="${SERVICE_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
    

    PROJECT_ID 替换为您的项目 ID。

  2. 使用您的项目 ID 初始化 gcloud 环境:

    gcloud config set project "${PROJECT_ID}"
    
  3. 创建三个具有唯一名称的 Cloud Storage 存储分区:

    gsutil mb -l "${LOCATION}" "gs://unscanned-${PROJECT_ID}"
    gsutil mb -l "${LOCATION}" "gs://quarantined-${PROJECT_ID}"
    gsutil mb -l "${LOCATION}" "gs://clean-${PROJECT_ID}"
    

    ${PROJECT_ID} 用于确保存储桶名称是唯一的。

    这三个存储桶用于在文件扫描流水线的各个阶段存放上传的文件:

    • unscanned-PROJECT_ID:存放尚未经过扫描的文件。您的用户会将其文件上传到此存储桶。

    • quarantined-PROJECT_ID:存放恶意软件扫描工具服务已扫描并认为其包含恶意软件的文件。

    • clean-PROJECT_ID:存放恶意软件扫描工具服务已扫描并发现其未受感染的文件。

  4. 创建第四个 Cloud Storage 存储桶:

    gsutil mb -l "${LOCATION}" "gs://cvd-mirror-${PROJECT_ID}"
    

    ${PROJECT_ID} 用于确保存储桶名称是唯一的。

    此存储桶 cvd-mirror-PROJECT_ID 用于维护恶意软件定义数据库的本地镜像,以防止 ClamAV CDN 触发速率限制。

为 malware-scanner 服务设置服务账号

在本部分中,您将创建用于恶意软件扫描工具服务的服务账号。然后,您需要向服务账号授予适当的角色,使其拥有 Cloud Storage 存储桶的读写权限。这些角色可确保账号具有最低权限并且仅可访问其需要的资源。

  1. 创建 malware-scanner 服务账号:

    gcloud iam service-accounts create ${SERVICE_NAME}
    
  2. 授予存储桶的 Object Admin 角色。此角色允许服务从未扫描的存储桶中读取和删除文件,以及将文件写入隔离的存储桶和干净的存储桶。

    gsutil iam ch \
        "serviceAccount:${SERVICE_ACCOUNT}:objectAdmin" \
        "gs://unscanned-${PROJECT_ID}"
    gsutil iam ch \
        "serviceAccount:${SERVICE_ACCOUNT}:objectAdmin" \
        "gs://clean-${PROJECT_ID}"
    gsutil iam ch \
        "serviceAccount:${SERVICE_ACCOUNT}:objectAdmin" \
        "gs://quarantined-${PROJECT_ID}"
    gsutil iam ch \
        "serviceAccount:${SERVICE_ACCOUNT}:objectAdmin" \
        "gs://cvd-mirror-${PROJECT_ID}"
    
  3. 授予 Metric Writer 角色,此角色允许服务将指标写入 Monitoring:

    gcloud projects add-iam-policy-binding \
          "${PROJECT_ID}" \
          --member="serviceAccount:${SERVICE_ACCOUNT}" \
          --role=roles/monitoring.metricWriter
    

在 Cloud Run 中创建 malware-scanner 服务

在本部分中,您需要将恶意软件扫描工具服务部署到 Cloud Run。该服务在包含以下组件的 Docker 容器中运行:

  • Dockerfile,用于使用服务、Node.js 运行时、Google Cloud SDK 和 ClamAV 二进制文件构建容器映像。
  • malware-scanner Cloud Run 服务的 Node.js 文件
  • config.json 配置文件,用于指定 Cloud Storage 存储桶名称。
  • updateCvdMirror.sh Shell 脚本,用于刷新 Cloud Storage 中的 ClamAV 恶意软件定义数据库镜像。
  • cloud-run-proxy 服务,用于代理 freshclam HTTP 请求,这些请求提供对 Cloud Storage API 经过身份验证的访问。
  • bootstrap.sh Shell 脚本,用于在实例启动时运行必要的服务。

如需部署服务,请执行以下操作:

  1. 在 Cloud Shell 中,克隆包含代码文件的 GitHub 代码库:

    git clone https://github.com/GoogleCloudPlatform/docker-clamav-malware-scanner.git
    
  2. 切换到 cloudrun-malware-scanner 目录:

    cd docker-clamav-malware-scanner/cloudrun-malware-scanner
    
  3. 修改 config.json 配置文件以指定您创建的 Cloud Storage 存储桶。由于存储桶名称基于项目 ID,因此您可以使用搜索和替换操作:

    sed "s/-bucket-name/-${PROJECT_ID}/" config.json.tmpl > config.json
    

    您可以查看更新后的配置文件:

    cat config.json
    
  4. 在 Cloud Storage 中执行 ClamAV 恶意软件数据库镜像的初始填充:

    python3 -m venv pyenv
    . pyenv/bin/activate
    pip3 install crcmod cvdupdate
    ./updateCvdMirror.sh "cvd-mirror-${PROJECT_ID}"
    deactivate
    

    该命令会执行 CVDUpdate 工具的本地安装,并使用它下载恶意软件数据库。然后,该命令会将数据库上传到您之前创建的 cvd-mirror-PROJECT_ID 存储桶。

    您可以查看镜像存储桶的内容:

    gsutil ls "gs://cvd-mirror-${PROJECT_ID}/cvds"
    

    该存储桶应包含多个 CVD 文件(包含完整的恶意软件数据库)、多个 .cdiff 文件(包含每日增量更新),以及两个 .json 文件(包含配置和状态信息)。

  5. 使用您之前创建的服务账号创建和部署 Cloud Run 服务:

    gcloud beta run deploy "${SERVICE_NAME}" \
      --source . \
      --region "${REGION}" \
      --no-allow-unauthenticated \
      --memory 4Gi \
      --cpu 1 \
      --concurrency 20 \
      --min-instances 1 \
      --max-instances 5 \
      --no-cpu-throttling \
      --cpu-boost \
      --service-account="${SERVICE_ACCOUNT}"
    

    该命令会创建一个具有 1 个 vCPU 并使用 4 GiB RAM 的 Cloud Run 实例。这个大小在此部署中是可以接受的。但在生产环境中,建议您为实例选择更大的 CPU 和内存大小,并选择更大的 --max-instances 参数。您可能需要的资源大小取决于服务需要处理的流量。

    该命令包含以下规范:

    • --concurrency 参数指定每个实例可以处理的并发请求数。
    • --no-cpu-throttling 参数使实例可以在后台执行操作,例如更新恶意软件定义。
    • --cpu-boost 参数会在实例启动时将 vCPU 数量加倍,以缩短启动延迟时间。
    • --min-instances 1 参数保证至少有一个实例处于活跃状态,因为每个实例都需要较长的时间来启动。
    • --max-instances 5 参数可防止服务过度纵向扩容。
  6. 出现提示时,输入 Y 以构建和部署服务。构建和部署大约需要 10 分钟时间才能完成。完成后,系统会显示以下消息:

    Service [malware-scanner] revision [malware-scanner-UNIQUE_ID] has been deployed and is serving 100 percent of traffic.
    Service URL: https://malware-scanner-UNIQUE_ID.a.run.app
    
  7. 将部署命令的输出中的 Service URL 值存储在一个 shell 变量中。稍后您在创建 Cloud Scheduler 作业时将使用该值。

    SERVICE_URL="SERVICE_URL"
    

如需查看正在运行的服务和 ClamAV 版本,请运行以下命令:

curl -D - -H "Authorization: Bearer $(gcloud auth print-identity-token)"  \
     ${SERVICE_URL}

Cloud Run 服务要求所有调用都经过身份验证,并且进行身份验证的身份必须具有服务的 run.routes.invoke 权限。您将在下一部分中添加该权限。

创建 Eventarc Cloud Storage 触发器

在本部分中,您将添加权限以允许 Eventarc 捕获 Cloud Storage 事件,并创建一个触发器以将这些事件发送到 Cloud Run malware-scanner 服务。

  1. 如果您使用的是在 2021 年 4 月 8 日之前创建的现有项目,请将 iam.serviceAccountTokenCreator 角色添加到 Pub/Sub 服务账号:

    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'
    

    添加此角色仅对于旧项目是必需的,并且允许 Pub/Sub 调用 Cloud Run 服务

  2. 在 Cloud Shell 中,向 Cloud Storage 服务账号授予 Pub/Sub Publisher 角色:

    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"
    
  3. 允许 malware-scanner 服务账号调用 Cloud Run 服务,并充当 Eventarc 事件接收器:

    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"
    
  4. 创建一个 Eventarc 触发器以捕获未扫描的 Cloud Storage 存储桶中的最终对象事件,并将其发送到您的 Cloud Run 服务。触发器使用 malware-scanner 服务账号进行身份验证:

    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}"
    

    如果您收到以下某个错误,请等待一分钟,然后再次运行该命令:

    ERROR: (gcloud.eventarc.triggers.create) INVALID_ARGUMENT: The request was invalid: Bucket "unscanned-PROJECT_ID" was not found. Please verify that the bucket exists.
    
    ERROR: (gcloud.eventarc.triggers.create) FAILED_PRECONDITION: Invalid resource state for "": Permission denied while using the Eventarc Service Agent. If you recently started to use Eventarc, it may take a few minutes before all necessary permissions are propagated to the Service Agent. Otherwise, verify that it has Eventarc Service Agent role.
    
  5. 在 Eventarc 触发器使用的底层 Pub/Sub 订阅中将消息确认时限更改为两分钟。对于大型文件或高负载,默认值 10 秒太短。

    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
    

    虽然触发器会立即创建,但触发器最长可能需要 10 分钟才能传播和过滤事件。

创建 Cloud Scheduler 作业以触发 ClamAV 数据库镜像更新

  • 创建一个 Cloud Scheduler 作业,该作业在 Cloud Run 服务上执行 HTTP POST 请求,并使用一个命令来更新恶意软件定义数据库的镜像。为避免过多客户端使用相同时段,ClamAV 要求您将作业安排在某个随机分钟运行,该数字在 3 到 57 之间的,并且避免 10 的倍数。

    while : ; do
      # set MINUTE to a random number between 3 and 57
      MINUTE="$((RANDOM%55 + 3))"
      # exit loop if MINUTE isn't a multiple of 10
      [[ $((MINUTE % 10)) != 0 ]] && break
    done
    
    gcloud scheduler jobs create http \
        "${SERVICE_NAME}-mirror-update" \
        --location="${REGION}" \
        --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"
    

    --schedule 命令行参数使用 unix-cron 字符串格式定义作业何时运行。给定值表示作业应该每两小时在随机生成的特定分钟运行一次。

此作业仅更新 Cloud Storage 中的 ClamAV 镜像。每个 Cloud Run 实例中的 ClamAV freshclam 守护程序每 30 分钟检查一次镜像,如果发现新定义,则会更新 ClamAV 守护程序。

通过上传文件测试流水线

如需测试流水线,请上传一个干净(不含恶意软件)的文件和一个模拟被感染文件的测试文件:

  1. 创建示例文本文件或使用现有的干净文件来测试流水线流程。

  2. 在 Cloud Shell 中,将示例数据文件复制到未扫描的存储桶:

    gsutil cp FILENAME "gs://unscanned-${PROJECT_ID}"
    

    FILENAME 替换为干净文本文件的名称。恶意软件扫描工具服务会检查每个文件并将其移到相应的存储桶。此文件已移到干净的存储桶。

  3. 给流水线几秒钟的时间来处理文件,然后检查您的干净存储桶,确认该存储桶中是否存在已处理的文件:

    gsutil ls -r "gs://clean-${PROJECT_ID}"
    

    您可以检查文件是否从未扫描的存储桶中移除:

    gsutil ls -r "gs://unscanned-${PROJECT_ID}"
    
  4. 将包含 EICAR 标准反恶意软件测试签名eicar-infected.txt 文件上传到未扫描的存储桶:

    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"
    

    此文本字符串包含的签名可触发恶意软件扫描工具,以进行测试。此测试文件是一项广泛使用的测试,它不是真正的恶意软件,对您的工作站无害。如果您尝试在安装了恶意软件扫描工具的计算机上创建包含此字符串的文件,将会触发提醒。

  5. 等待几秒钟,然后检查隔离的存储桶,以查看文件是否已成功通过了流水线:

    gsutil ls -r "gs://quarantined-${PROJECT_ID}"
    

    如果检测到被恶意软件感染的文件,则该服务还会记录 Logging 日志条目。

    您可以检查文件是否从未扫描的存储桶中移除:

    gsutil ls -r "gs://unscanned-${PROJECT_ID}"
    

测试恶意软件定义数据库更新机制

  • 在 Cloud Shell 中,通过强制运行 Cloud Scheduler 作业来触发更新检查:

    gcloud scheduler jobs run "${SERVICE_NAME}-mirror-update" --location="${REGION}"
    

    此命令的结果仅在详细日志中显示。

监控服务

您可以使用 Cloud Logging 和 Cloud Monitoring 来监控服务。

查看详细日志

  1. 在 Google Cloud 控制台中,转到 Cloud Logging 日志浏览器页面。

    转到日志浏览器

  2. 如果未显示日志字段过滤条件,请点击日志字段

  3. 日志字段过滤条件中,点击 Cloud Run 修订版本

  4. 日志字段过滤条件的服务名称部分中,点击 malware-scanner

日志查询结果显示服务的日志,其中几行显示您上传的两个文件的扫描请求和状态:

Scan request for gs://unscanned-PROJECT_ID/FILENAME, (##### bytes) scanning with clam ClamAV CLAMAV_VERSION_STRING
Scan status for gs://unscanned-PROJECT_ID/FILENAME: CLEAN (##### bytes in #### ms)
...
Scan request for gs://unscanned-PROJECT_ID/eicar-infected.txt, (69 bytes) scanning with clam ClamAV CLAMAV_VERSION_STRING
Scan status for gs://unscanned-PROJECT_ID/eicar-infected.txt: INFECTED stream: Eicar-Signature FOUND (69 bytes in ### ms)

输出会显示 ClamAV 版本和恶意软件数据库签名修订版本,以及被感染测试文件的恶意软件名称。您可以使用这些日志消息来设置发现恶意软件或扫描失败时的提醒。

输出结果还会显示恶意软件定义镜像更新日志:

Starting CVD Mirror update
CVD Mirror update check complete. output: ...

如果镜像已更新,则输出会显示额外的行:

CVD Mirror updated: DATE_TIME - INFO: Downloaded daily.cvd. Version: VERSION_INFO

freshclam 更新日志每 30 分钟显示一次:

DATE_TIME -> Received signal: wake up
DATE_TIME -> ClamAV update process started at DATE_TIME
DATE_TIME -> daily.cvd database is up-to-date (version: VERSION_INFO)
DATE_TIME -> main.cvd database is up-to-date (version: VERSION_INFO)
DATE_TIME -> bytecode.cvd database is up-to-date (version: VERSION_INFO)

如果数据库已更新,则 freshclam 日志行将如下所示:

DATE_TIME -> daily.cld updated (version: VERSION_INFO)

查看指标

该服务会生成以下用于监控和提醒目的的指标:

  • 已处理的清洁文件数:
    custom.googleapis.com/opencensus/malware-scanning/clean_files
  • 已处理的受感染文件数:
    custom.googleapis.com/opencensus/malware-scanning/infected_files
  • 扫描文件所花费的时间:
    custom.googleapis.com/opencensus/malware-scanning/scan_duration
  • 扫描的字节总数:
    custom.googleapis.com/opencensus/malware-scanning/bytes_scanned
  • 失败的恶意软件扫描次数:
    custom.googleapis.com/opencensus/malware-scanning/scans_failed
  • CVD 镜像更新检查次数:
    custom.googleapis.com/opencensus/malware-scanning/cvd-mirror-updates

您可以在 Cloud Monitoring Metrics Explorer 中查看这些指标:

  1. 在 Google Cloud 控制台,转到 Cloud Monitoring Metrics Explorer 页面。

    打开 Metrics Explorer

  2. 点击选择指标字段,然后输入过滤条件字符串 malware

  3. 选择 OpenCensus/malware-scanning/clean_files 指标。该图表会显示一个数据点,指明干净文件的扫描时间。

您可以使用指标来监控流水线,以及在检测到恶意软件或文件处理失败时创建提醒。

生成的指标具有以下标签,您可以使用这些标签进行过滤和汇总,以通过 Metrics Explorer 查看更精细的信息:

  • source_bucket
  • destination_bucket
  • clam_version
  • cloud_run_revision

处理多个存储桶

恶意软件扫描工具服务可以扫描多个来源存储桶中的文件,并将文件发送到单独的干净存储桶和隔离存储桶。此高级配置超出了此部署的范围,但我们将所需步骤总结如下:

  1. 创建具有唯一名称的未扫描、干净和隔离的 Cloud Storage 存储桶。

  2. malware-scanner 服务账号授予每个存储桶的适当角色

  3. 修改 config.json 配置文件以为每个配置指定存储桶名称:

    {
      "buckets": [
        {
          "unscanned": "unscanned-bucket-1-name",
          "clean": "clean-bucket-1-name",
          "quarantined": "quarantined-bucket-1-name"
        },
        {
          "unscanned": "unscanned-bucket-2-name",
          "clean": "clean-bucket-2-name",
          "quarantined": "quarantined-bucket-2-name"
        }
      ]
      "ClamCvdMirrorBucket": "cvd-mirror-bucket-name"
    }
    
  4. 对于每个未扫描的存储桶,创建 Eventarc 触发器。请确保为每个存储桶创建唯一的触发器名称。

    Cloud Storage 存储桶必须与 Eventarc 触发器位于同一项目和区域中。

清理

以下部分介绍如何避免此部署中使用的 Google Cloud 项目未来产生费用。

删除 Google Cloud 项目

为避免系统因此部署中使用的资源向您的 Google Cloud 账号收取费用,您可以删除 Google Cloud 项目。

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤