GCP Serverless Scanner
Deploy Mondoo's serverless scanner into your own GCP project with Terraform to continuously scan an organization or folder using Cloud Run, including options for CMEK, air-gapped installs, and restricted org policies.
The GCP serverless scanner runs entirely inside your GCP project. A Cloud Run service (the orchestrator) discovers the projects in a configured organization or folder on a schedule, then fans out one Cloud Run Job execution per project to run cnspec and ship results to Mondoo. Nothing scans from Mondoo's side, and no long-lived service account key is stored when you use Workload Identity Federation.
Looking for the platform-managed option instead? The GCP integration has Mondoo scan your projects for you. The serverless scanner is the self-hosted alternative: you deploy and own the scanning infrastructure with Terraform, which suits large organizations and restricted environments where scanning must originate from inside the tenancy.
What gets deployed
A single terraform apply of the serverless-scanner-gcp module
(deploy/terraform) creates three Cloud Run resources plus the Pub/Sub, Cloud
Storage, IAM, and (optionally) Cloud Scheduler scaffolding that connects them:
- Orchestrator, a Cloud Run service that receives schedule ticks, enumerates the projects in scope, and dispatches one scan task per project.
- Scanner, a Cloud Run Job. The orchestrator launches one execution per project; each execution runs cnspec against that project. The Job uses a Google Cloud SDK base image that downloads a per-execution scan script from the orchestrator bucket and runs it, so there is no cnspec image to pull.
- Updater, a Cloud Run Job that polls Mondoo's registry for a newer orchestrator image and rolls the service to it, so you do not have to re-apply after each release.
Prerequisites
- A dedicated GCP host project (separate from the projects you scan) with these
APIs enabled:
run,pubsub,iam,cloudresourcemanager,logging,secretmanager,storage, andmonitoring. Addcloudscheduleronly if you keep the built-in scheduler (seeenable_scheduler). The module verifies the APIs read-only and stops the plan if one is missing; it never enables them for you. - A Mondoo GCP serverless integration created in the console, which gives you an integration MRN and a credential (a Workload Identity Federation config or a service account JSON).
- An Artifact Registry repository the module's images are served from.
- The runtime service account's read access to the organization or folder you
scan. The module can grant this, or you can grant it out-of-band (see
manage_scope_iam).
Usage
module "serverless_scanner" {
source = "github.com/mondoohq/serverless-scanner-gcp//deploy/terraform"
project_id = "my-scanner-host"
region = "europe-west3"
image = "europe-west3-docker.pkg.dev/my-scanner-host/mondoo/orchestrator:prod"
# What to scan and where results go.
scope = "organizations/123456789012" # or folders/<id>
mondoo_integration_mrn = "//integrations.api.mondoo.com/spaces/<space>/integrations/<id>"
mondoo_config_base64 = filebase64("mondoo.json") # WIF config or SA JSON
}Configuration reference
Core inputs
| Input | Description |
|---|---|
project_id | The GCP host project the scanner runs in. |
region | Region for the Cloud Run service, Jobs, and Pub/Sub. |
image | Orchestrator (and updater) container image URI. |
scope | The org or folder to discover and scan, as organizations/<id> or folders/<id>. |
mondoo_integration_mrn | The integration MRN results report against. |
mondoo_config_base64 | Base64-encoded Mondoo credential (WIF config preferred, or a service account JSON). |
service_account_email | An existing runtime service account to use. Empty makes the module create one. |
name_prefix | Prefix for resource names, so one project can host multiple deployments. |
Schedules
The orchestrator is driven by four triggers, each a cron expression interpreted
in UTC: scan_schedule (the scan pass, every 12 hours by default),
metrics_schedule (telemetry), sync_schedule (backend-config sync), and
checkin_schedule (a liveness ping, every 10 minutes by default).
auto_update_schedule controls the updater (hourly by default).
Options for restricted environments
The following inputs exist so the module can deploy into sovereign, Assured Workloads, or otherwise policy-restricted projects. Each defaults to the unrestricted behavior, so leaving them unset changes nothing.
| Input | Default | Purpose |
|---|---|---|
kms_key_name | "" | Customer-managed encryption for all data-plane resources. |
installer_url | "" | Internal mirror for cnspec and provider downloads. |
scanner_image | Google Cloud SDK | Override the scanner Job base image. |
enable_scheduler | true | Create the Cloud Scheduler jobs, or drive triggers externally. |
manage_scope_iam | true | Grant the runtime SA its org/folder scan roles, or do it out-of-band. |
manage_project_iam | true | Grant the runtime SA its host-project roles, or do it out-of-band. |
Restricted and sovereign environments
Locked-down GCP organizations (sovereign clouds, Assured Workloads) enforce organization policies that reject the module's default resources. The options below make each requirement satisfiable.
Customer-managed encryption (CMEK)
Where constraints/gcp.restrictNonCmekServices is enforced, resources created
without a customer-managed key are rejected. Set kms_key_name to a Cloud KMS
key and the module encrypts the orchestrator bucket, every Pub/Sub topic, the
Cloud Run service and Jobs, and the config secret with it. The secret switches to
regional (user_managed) replication in region, since automatic replication
cannot use a single regional key.
kms_key_name = "projects/<key-project>/locations/europe-west3/keyRings/<ring>/cryptoKeys/<key>"The module does not manage IAM on the key. The Cloud Storage, Pub/Sub, Cloud Run, and Secret
Manager service agents of project_id must hold roles/cloudkms.cryptoKeyEncrypterDecrypter on
the key before the apply, or resource creation fails with a KMS permission error. Grant this
out-of-band, since the key commonly lives in a separate, centrally-managed project. Also confirm
constraints/gcp.restrictCmekCryptoKeyProjects allows the key's project, and that the key's
location matches region.
Air-gapped scan workloads
When the scan workloads cannot reach the public internet, set installer_url to
an internal mirror. The scan then fetches the cnspec binary from
<installer_url>/package/... and its providers from <installer_url>/providers/...,
so the mirror must serve both path layouts under one base URL.
installer_url = "https://install.mirror.example.internal"If the host project also blocks Cloud Run from pulling the default gcr.io
scanner base image, mirror a Google Cloud SDK image into your registry and set
scanner_image to it.
Driving triggers without Cloud Scheduler
Where Cloud Scheduler is unavailable (for example, org policy blocks the App
Engine service family), set enable_scheduler = false. The module then creates
no scheduler jobs and does not require the cloudscheduler API. The scan-trigger
Pub/Sub topic still exists, so an external driver, such as a Kubernetes CronJob,
can publish the typed trigger payloads to it on your own schedule.
With enable_scheduler = false, nothing in the module is granted publish access on the trigger
topic. Grant your external publisher roles/pubsub.publisher on the topic (exposed as the
module's scan_trigger_topic output).
Delegating IAM when the deployer cannot set it
In delegated-admin setups, the identity running Terraform may not set folder, organization, or project IAM, even though it can create the resources themselves. Two flags move those grants out-of-band:
manage_scope_iam = false: the module does not grant the runtime SA its org/folder scan roles. Grant them yourself at the scope node.manage_project_iam = false: the module does not grant the runtime SA (or the Cloud Run service agent) their host-project roles. Grant them yourself in the host project.
When manage_project_iam = false, grant these in the host project out-of-band:
| Member | Roles |
|---|---|
| Runtime service account | roles/browser, roles/viewer, roles/iam.securityReviewer, roles/resourcemanager.tagViewer, roles/logging.viewer, roles/monitoring.viewer, roles/run.viewer, roles/run.developer, roles/artifactregistry.reader |
Cloud Run service agent (service-<project-number>@serverless-robot-prod.iam.gserviceaccount.com) | roles/artifactregistry.reader |
The service agent's roles/artifactregistry.reader is required for the first revision to pull its
image. If you set manage_project_iam = false and forget it, the orchestrator, scanner, and
updater fail to deploy with a generic image pull error.
Resource-level IAM (Pub/Sub publisher, Cloud Run invoker, Secret Manager accessor) is always managed by the module and is unaffected by these flags.