Cloud SecurityCNAPPIaCGoogle CloudSlack

Monitoring Your Crown Jewels in Google Cloud with Cloud Asset Inventory and Slack

Jorge Liauw Calo
Jorge Liauw CaloSecurity Engineer @ Google Cloud
•17 min read
Monitoring Your Crown Jewels in Google Cloud with Cloud Asset Inventory and Slack

Why preventive IAM isn’t always enough for your most sensitive resources, and how to catch every critical change in Slack within seconds.

In almost every cloud security discussion I have, we start with preventive controls: enforce the principle of least privilege with IAM, lock down the environment with Organization Policy Constraints, and make sure every change goes through Terraform.

In theory, if you lock everything down tightly enough, nothing bad can happen. But anyone who has worked in a real production environment knows there is a catch.

The “Why”: Engineers Still Need Access to Your Crown Jewels

Even in an organization that does least privilege really well, you always have a group of engineers, SREs, network specialists, DBAs, break-glass responders, and CI/CD service accounts that must have permissions to touch your most critical resources.

You cannot just strip away their IAM roles. They need that access to do their day-to-day work, onboard new services, maintain databases, or fix a production outage at 2:00 AM.

At the same time, these resources are your Crown Jewels. They are simply too sensitive to leave unmonitored. When an engineer or a pipeline has legitimate access to modify a production firewall, a Compute Engine VM, a Cloud SQL instance, a Secret Manager vault or Cloud KMS key, or an IAM policy, preventive IAM stops helping you. What you need at that point is continuous, real-time change monitoring:

  • Did someone open a firewall rule to a new IP address during a debugging session and forget to revert it?
  • Was that change rolled out cleanly through Terraform (IaC), or did someone click around in the Google Cloud Console (ClickOps) or run a manual gcloud command?
  • Did a specific user or service account just get extra IAM privileges on a sensitive project or secret?

To solve this in a clean, serverless way, I built and open-sourced google-cloud-cai-asset-change-notifications on GitHub. It uses Cloud Asset Inventory (CAI), which is part of Security Command Center (SCC), to monitor changes on your Crown Jewels in real time and send rich, human-readable alerts straight to Slack.

Why Cloud Asset Inventory (Part of Security Command Center) Is So Useful Here

A lot of teams try to build change monitoring using raw log sinks in Cloud Logging. The problem with raw audit logs is that an API call log only shows you the request parameters that were sent—it rarely shows you what the resource actually looked like before the change versus after the change.

Cloud Asset Inventory (CAI)—a core capability within Google Cloud’s Security Command Center and CNAPP posture management—solves this natively. Whenever a resource (RESOURCE) or IAM policy (IAM_POLICY) is created, updated, or deleted, CAI streams a real-time payload to Pub/Sub that contains both:

  1. priorAsset — the exact configuration or IAM policy before the change.
  2. asset — the exact configuration or IAM policy after the change.

In my notifier, a 2nd-generation Cloud Run function takes that CAI payload, computes a clean before-and-after diff, and then correlates the event with Cloud Audit Logs (entries:list) so your security team immediately sees:

  • What changed: The exact field, IP address, security flag, or IAM role binding that was added, removed, or modified.
  • Who did it: Whether it was a human user (User Account), a CI/CD Service Account, Workload Identity, or a user impersonating a service account (User impersonating Service Account via serviceAccountDelegationInfo).
  • How it was executed: Whether the change came through Terraform (IaC) (or Pulumi/Crossplane/Ansible), the Google Cloud Console (Web UI / ClickOps), or the gcloud CLI with the exact command executed—including an automatic drift warning whenever someone makes a manual change outside of IaC.

Key Security Monitoring Use Cases

When you set up continuous monitoring for your Crown Jewels, you do not want alerts for every ephemeral development resource spinning up and down. By default, var.monitored_asset_types and the function’s posture analyzer (analyze_security_posture) focus on five high-impact categories where a single configuration change alters your security posture:

1. Firewall Rules & VPC Networking: New IP Addresses, Port Changes, or Disabled Logging

Network and platform engineers regularly need to update firewall rules (compute.googleapis.com/Firewall, compute.googleapis.com/FirewallPolicy) and VPC networks (compute.googleapis.com/Network, Subnetwork, Route, Router). With CAI change notifications, you get an immediate Slack message whenever:

  • A new IP address or CIDR block is added to or removed from sourceRanges or destinationRanges (or worse, 0.0.0.0/0 or ::/0 is opened to the public internet).
  • Allowed or denied port ranges and protocols (allowed / denied) are modified.
  • Someone disables Firewall Rule Logging (logConfig.enable: true -> false) or turns off VPC Flow Logs (enableFlowLogs) or Private Google Access (privateIpGoogleAccess) on a subnetwork.

2. IAM Privilege Escalation, Added Roles & Service Account Keys

Privilege escalation rarely happens by someone granting roles/owner out of nowhere. Most of the time, it happens incrementally: an existing user or service account with baseline permissions (like roles/logging.logWriter) gets additional roles added (roles/iam.serviceAccountTokenCreator, roles/bigquery.admin, or roles/secretmanager.secretAccessor), or a custom IAM role (iam.googleapis.com/Role) has new permissions quietly added to includedPermissions.

By comparing priorAsset.iamPolicy and asset.iamPolicy both per role and per principal across Organization, Folder, Project, and resource policies, the notifier flags:

  • Per-principal IAM privilege escalation when an existing user: or serviceAccount: gains additional roles on top of what they previously held.
  • Newly added IAM roles and bindings, with dedicated risk signals for privileged roles (PRIVILEGED_ROLES) and public exposure (allUsers / allAuthenticatedUsers).
  • Custom IAM role permission expansion (includedPermissions on iam.googleapis.com/Role) and creation of user-managed Service Account keys (iam.googleapis.com/ServiceAccountKey).

3. Cloud SQL & Sensitive Data Stores: Weakened SSL or Expanded Network Access

Production databases (sqladmin.googleapis.com/Instance) and data stores (bigquery.googleapis.com/Dataset, bigquery.googleapis.com/Table, storage.googleapis.com/Bucket) hold your actual business data. DBAs and platform engineers often need admin access to maintain these instances, which means they can also change security access settings. You get an instant Slack alert if:

  • Database SSL/TLS enforcement is disabled on Cloud SQL (requireSsl is flipped to false, or sslMode is downgraded from ENCRYPTED_ONLY to ALLOW_UNENCRYPTED_AND_ENCRYPTED).
  • Database network access IP ranges are expanded (settings.ipConfiguration.authorizedNetworks gets a new external CIDR or 0.0.0.0/0 added, or public ipv4Enabled is enabled).
  • BigQuery dataset ACLs are expanded (new bindings added to resource.data.access or public access granted to allUsers / allAuthenticatedUsers).

4. Compute Engine VMs & GKE Clusters: External IPs, Shielded VM & Machine Series Drift

For production compute resources (compute.googleapis.com/Instance, compute.googleapis.com/InstanceTemplate, container.googleapis.com/Cluster), the notifier inspects every configuration change for:

  • External public IP attachments (ONE_TO_ONE_NAT added in networkInterfaces.accessConfigs).
  • Disabled Shielded VM or Confidential Compute controls (enableSecureBoot, enableVtpm, enableIntegrityMonitoring, or enableConfidentialCompute turned off).
  • Machine type / CPU series drift (for example, changing n2d-standard-4 to e2-standard-8, alongside a dedicated app/monitor-cpu-machine-type/ function for enforcing approved machine families).

5. Secret Manager Vaults & Cloud KMS Encryption Keys

Cryptographic keys (cloudkms.googleapis.com/CryptoKey, KeyRing) and secrets (secretmanager.googleapis.com/Secret, SecretVersion) are prime Crown Jewels. The notifier alerts on any creation, rotation/version update, deletion, or IAM policy change (roles/secretmanager.secretAccessor, roles/cloudkms.cryptoKeyDecrypter), while automatically redacting sensitive payload fields (secretData, payload, privateKeyData, rawKey, password, token) so secret values never leak into Slack.

What the Notifications Look Like in Slack

Here are three examples (generated directly from the repository’s test fixtures) of what lands in your Slack channel when a Crown Jewel resource or its IAM policy is modified.

1. Firewall Rule Modified with a New IP Range via Google Cloud Console (ClickOps)

🛡️ CAI & SCC Security Notifier APP #security-gcp-alerts • 01:15 UTC

🚨 Crown Jewel Asset Alert: compute.googleapis.com/Firewall allow-prod-ssh-ingress was MODIFIED

Resource: allow-prod-ssh-ingress | Resource type: compute.googleapis.com/Firewall
Project: prod-networking-01 | Location: global
Action: MODIFIED ✏️ | Event time: 2026-09-27T01:15:42.000Z
Change Attribution (Cloud Audit Logs):
• Who (Actor): [email protected] (User Account)
• How (Executed via): Google Cloud Console (Web UI / ClickOps) ⚠️ ClickOps Manual Change
• API Method: v1.compute.firewalls.patch
• Caller IP: 198.51.100.42 | View Audit Trail in Cloud Logging ↗
Security Posture & Risk Signals:
• ⚠️ Manual Change Outside IaC (ClickOps / CLI): Executed via Google Cloud Console (Web UI / ClickOps) ⚠️ by [email protected] (User Account).
• 🚨 Public Internet Ingress (0.0.0.0/0): Firewall rule exposes tcp:22,3389 to the public internet.
• ⚠️ Firewall Source IP Ranges Modified: added 0.0.0.0/0, removed 10.128.0.0/16.
• ⚠️ Firewall Allowed Port Ranges / Protocols Changed: tcp:22 -> tcp:22,3389.
Change Diff (Cloud Asset Inventory):
~ allowed: [{"IPProtocol": "tcp", "ports": ["22"]}] -> [{"IPProtocol": "tcp", "ports": ["22", "3389"]}]
+ sourceRanges: added ["0.0.0.0/0"]
- sourceRanges: removed ["10.128.0.0/16"]
Recommendation: Verify whether this manual change was authorized under an emergency break-glass procedure and reconcile it into Terraform to prevent state drift. Restrict sourceRanges to trusted corporate CIDRs, Identity-Aware Proxy (35.235.240.0/20), or internal VPC ranges.

2. Cloud SQL SSL Enforcement Disabled & Authorized Network IP Range Expanded

🛡️ CAI & SCC Security Notifier APP #security-gcp-alerts • 01:45 UTC

🚨 Crown Jewel Asset Alert: sqladmin.googleapis.com/Instance prod-customer-orders-sql was MODIFIED

Resource: prod-customer-orders-sql | Resource type: sqladmin.googleapis.com/Instance
Project: prod-data-01 | Location: europe-west1
Action: MODIFIED ✏️ | Event time: 2026-09-27T01:45:30.000Z
Change Attribution (Cloud Audit Logs):
• Who (Actor): [email protected] (User Account)
• How (Executed via): Google Cloud Console (Web UI / ClickOps) ⚠️
• API Method: cloudsql.instances.update | Caller IP: 198.51.100.91 | View Audit Trail in Cloud Logging ↗
Security Posture & Risk Signals:
• ⚠️ Manual Change Outside IaC (ClickOps / CLI): Executed via Google Cloud Console (Web UI / ClickOps) ⚠️ by [email protected] (User Account).
• 🚨 Database SSL Enforcement Disabled: Unencrypted connections are permitted on this Cloud SQL instance (sslMode: ENCRYPTED_ONLY -> ALLOW_UNENCRYPTED_AND_ENCRYPTED).
• ⚠️ Database Network Access IP Range Expanded: Added authorized network CIDR(s) 198.51.100.0/24 (external-vendor-subnet) to Cloud SQL instance.
Change Diff (Cloud Asset Inventory):
+ settings.ipConfiguration.authorizedNetworks: added [{"name": "external-vendor-subnet", "value": "198.51.100.0/24"}]
~ settings.ipConfiguration.requireSsl: true -> false
~ settings.ipConfiguration.sslMode: "ENCRYPTED_ONLY" -> "ALLOW_UNENCRYPTED_AND_ENCRYPTED"
Recommendation: Verify whether this manual change was authorized under an emergency break-glass procedure and reconcile it into Terraform to prevent state drift. Re-enable SSL enforcement (requireSsl = true and sslMode = ENCRYPTED_ONLY or TRUSTED_CLIENT_CERTIFICATE_REQUIRED) on the Cloud SQL instance.

3. IAM Privilege Escalation & Added Role via gcloud CLI

🛡️ CAI & SCC Security Notifier APP #security-gcp-alerts • 01:40 UTC

🚨 Crown Jewel Asset Alert: cloudresourcemanager.googleapis.com/Project prod-core-01 was IAM_POLICY_UPDATED

Resource: prod-core-01 | Resource type: cloudresourcemanager.googleapis.com/Project
Project: prod-core-01 | Location: global
Action: IAM_POLICY_UPDATED 🔑 | Event time: 2026-09-27T01:40:12.000Z
Change Attribution (Cloud Audit Logs):
• Who (Actor): [email protected] (User Account)
• How (Executed via): gcloud CLI (gcloud projects add-iam-policy-binding) ⚠️
• API Method: SetIamPolicy | Caller IP: 198.51.100.77 | View Audit Trail in Cloud Logging ↗
Security Posture & Risk Signals:
• ⚠️ Manual Change Outside IaC (ClickOps / CLI): Executed via gcloud CLI (gcloud projects add-iam-policy-binding) ⚠️ by [email protected] (User Account).
• 🔑 Added IAM Role(s): roles/bigquery.admin, roles/iam.serviceAccountTokenCreator added to policy.
• 🚨 IAM Privilege Escalation: serviceAccount:[email protected] gained roles/iam.serviceAccountTokenCreator (in addition to existing roles/logging.logWriter).
• 🔑 Privileged IAM Role Granted: roles/iam.serviceAccountTokenCreator granted to serviceAccount:[email protected].
• 🔑 Privileged IAM Role Granted: roles/bigquery.admin granted to user:[email protected].
Change Diff (Cloud Asset Inventory):
+ IAM Role Added: roles/bigquery.admin -> ['user:[email protected]']
+ IAM Role Added: roles/iam.serviceAccountTokenCreator -> ['serviceAccount:[email protected]']
~ Privilege Escalation (serviceAccount:[email protected]): gained ['roles/iam.serviceAccountTokenCreator'] (existing: ['roles/logging.logWriter'])
Recommendation: Verify whether this manual change was authorized under an emergency break-glass procedure and reconcile it into Terraform to prevent state drift.

How It Works Under the Hood

Just like my SCC findings notifier for Slack, the architecture is completely serverless and deployed with Terraform:

  1. Cloud Asset Inventory Feeds (RESOURCE & IAM_POLICY): Terraform provisions organization-wide CAI feeds (crown_jewels_resources and crown_jewels_iam), plus an optional folder-scoped feed (var.folder_id) and optional SCC v2 finding notification config (var.enable_scc_notification), all streaming into a single Pub/Sub topic (cai-asset-changes-topic).
  2. Eventarc & Cloud Run Function (2nd Gen): Eventarc triggers the cai-slack-notifier Python 3.13 Cloud Run function (entry point send_slack_chat_notification), locked down with ALLOW_INTERNAL_ONLY ingress and dedicated least-privilege service accounts (cainotifier for runtime/trigger and cainotifier-build for Cloud Build).
  3. CAI Diff Engine, Deduplication & Cloud Audit Logs Lookup: The function strips out volatile metadata fields (etag, fingerprint, selfLink, creationTimestamp), redacts sensitive fields (secretData, payload, privateKeyData, rawKey, password, token), skips metadata-only updates with zero structural diff, deduplicates burst alerts within a configurable cooldown window (DEDUP_WINDOW_SECONDS), and queries Cloud Audit Logs (entries:list) to determine Who made the change and How it was executed. The repository also includes specialized monitors for CPU machine-type drift (app/monitor-cpu-machine-type/), project/org IAM principals (app/monitor-principals/), and Service Account IAM policies (app/sa-iam-monitor/).
  4. Cloud KMS & Secret Manager for the Slack Token: You never store a plaintext Slack token in Git or terraform.tfvars. Stage 1 (infra/kms) creates a Cloud KMS key (prevent_destroy = true) to encrypt your Slack bot token locally, and Stage 2 (infra/) decrypts it into Secret Manager (cainotifier-slack-bot-token) for the Cloud Run function.

Try It Out in Your Own Slack Channel

If you want to see how these alerts look in your own Slack workspace before deploying any infrastructure in Google Cloud, the repository includes five realistic test fixtures that you can preview locally or send straight to Slack from your terminal:

# Demo 1: VPC Firewall Rule opened to 0.0.0.0/0 via Google Cloud Console (ClickOps)
SLACK_BOT_TOKEN=xoxb-your-token \
SLACK_CHANNEL=C0123456789 \
python3 app/cai-asset-change-notifications/main.py tests/fixtures/firewall_open_ssh_console.json --send

# Demo 2: Compute Engine VM with External IP & Machine Type changed via gcloud CLI (SA Impersonation)
SLACK_BOT_TOKEN=xoxb-your-token \
SLACK_CHANNEL=C0123456789 \
python3 app/cai-asset-change-notifications/main.py tests/fixtures/compute_vm_external_ip_gcloud.json --send

# Demo 3: Secret Manager IAM Policy modified via Terraform (IaC)
SLACK_BOT_TOKEN=xoxb-your-token \
SLACK_CHANNEL=C0123456789 \
python3 app/cai-asset-change-notifications/main.py tests/fixtures/secret_manager_iam_terraform.json --send

# Demo 4: IAM Privilege Escalation (existing Service Account gains TokenCreator + added BigQuery Admin role)
SLACK_BOT_TOKEN=xoxb-your-token \
SLACK_CHANNEL=C0123456789 \
python3 app/cai-asset-change-notifications/main.py tests/fixtures/iam_privilege_escalation.json --send

# Demo 5: Cloud SQL Database SSL Enforcement Disabled & Network Access IP Range Expanded
SLACK_BOT_TOKEN=xoxb-your-token \
SLACK_CHANNEL=C0123456789 \
python3 app/cai-asset-change-notifications/main.py tests/fixtures/cloudsql_ssl_and_network_expanded.json --send

Deploying the full solution across your Google Cloud organization takes two quick Terraform steps:

# 1. Stage 1: KMS key for encrypting the Slack bot token
cd infra/kms && cp terraform.tfvars.example terraform.tfvars   # edit project_id & members
terraform init && terraform apply

# 2. Encrypt the Slack bot token with Cloud KMS
read -rs SLACK_BOT_TOKEN && export SLACK_BOT_TOKEN
terraform output -raw encrypt_command | sh && unset SLACK_BOT_TOKEN

# 3. Stage 2: Deploy the CAI & SCC Crown Jewel notifier
cd .. && cp terraform.tfvars.example terraform.tfvars          # edit, paste the KMS ciphertext
terraform init && terraform apply

Get the Code on GitHub

You can find the complete repository, Terraform modules, and all sample payloads on my GitHub:

👉 github.com/jorgecalo/google-cloud-cai-asset-change-notifications

If you are setting up Crown Jewel monitoring in your Google Cloud organization or want to share additional asset types you monitor in your environment, feel free to reach out on LinkedIn or via my contact page!

Jorge Liauw Calo
Written By

Jorge Liauw Calo

Security Engineer at Google Cloud (Google Cybershield) with 13+ years of experience in Cybersecurity across highly regulated industries including Semiconductor, Banking, Fintech, and Insurance. Active member of the Google Cloud Community BeNeLux and Google Cloud Security Community Amsterdam.