CoursesGCP securitySecurity Command Center

Security Command Center

CSPM plus threat detection, org-wide.

Advanced30 min · lesson 11 of 15

A campus of a dozen buildings runs its security from one room. One wall of screens. One operator. Every fire alarm, forced door and camera from every building lands on that wall. Nobody wants to walk twelve buildings checking twelve separate panels while something is burning in building nine. Security Command Center (SCC, Google's organization-wide security console) is that wall for your Google Cloud org. You stop opening projects one at a time to ask whether a storage bucket went public, or whether somebody quietly handed themselves owner rights. You get one feed of findings instead, all in the same format, ranked by severity, covering everything you run.

The load-bearing word there is organization. Google Cloud stacks your resources in a tree: the organization at the top, folders hanging under it, projects sitting under the folders, the way a filing cabinet holds drawers that hold folders. SCC clamps onto the top of that tree. Everything below it is covered by default, including the projects nobody has created yet. Coverage becomes a property the org has, not a checkbox somebody has to remember per project. That closes the oldest blind spot in the business, the one where detection is on in nine projects and off in the tenth. The tenth is always the one that gets popped.

Three detectors, one wall

Three engines feed that wall, and they are looking at completely different things. Security Health Analytics is the building inspector. It walks the halls on a schedule and rattles the doors: buckets left open to the public, firewall rules that let in the entire internet (that is what 0.0.0.0/0 means), audit logging switched off, a service account (a login used by software rather than a person) holding full owner rights. It reads configuration, the state of things, never events. Event Threat Detection (ETD) is the detective reading the logbook. Every action in your cloud gets written into a running diary called Cloud Logging, and ETD streams that diary looking for suspicious sequences: an outside Gmail account handed a sensitive role, a service account slipped into a privileged group, a virtual machine calling a known crypto-mining server. Container Threat Detection is the guard standing inside the room, watching what the workers actually do. A hijacked command line phoning home to an attacker (a reverse shell) inside one of your pods on GKE (Google Kubernetes Engine, Google's managed platform for running containers), or an unexpected program starting up mid-run: it catches those live. Three vantage points. One screen.

All three write findings in the same shape, and that sameness is the quiet win. Each finding carries a severity, a findingClass (THREAT means something is happening right now, MISCONFIGURATION means something is set up badly), a resourceName pointing at what is affected, and a timestamp. So a responder can weigh a live reverse shell against a public bucket against an odd permission grant without learning three different data formats. Config scanner, log miner and runtime sensor all speak one language on the wall.

None of the good parts are free. The Standard tier costs nothing and is thin: new activations mostly get basic web scanning, and only orgs migrated from the old free tier still see a slice of Security Health Analytics. Both threat detectors, ETD and Container Threat Detection, start at Premium, and so does full posture scanning. Premium's pay-as-you-go pricing has historically scaled with your total Google Cloud spend, so a bigger footprint costs you more. Model that before you switch it on across the whole org. Enterprise sits above Premium. It reaches into AWS and Azure so those clouds land on the same wall, and it pipes findings straight into Google SecOps, Google's SIEM and SOAR stack. (A SIEM, security information and event management, gathers and correlates alerts from everywhere; SOAR, security orchestration, automation and response, is the automation that then acts on them.) Enterprise is priced per protected workload instead of as a cut of your whole bill.

stream HIGH/CRITICAL findings org-wide to Pub/Sub
$ gcloud scc notifications create highsev-findings \
--organization=123456789012 \
--description="High and critical findings to central SIEM" \
--pubsub-topic=projects/sec-central/topics/scc-findings \
--filter='severity="HIGH" OR severity="CRITICAL"'
Created notification config [organizations/123456789012/notificationConfigs/highsev-findings].
name: organizations/123456789012/notificationConfigs/highsev-findings
description: High and critical findings to central SIEM
pubsubTopic: projects/sec-central/topics/scc-findings
streamingConfig:
filter: severity="HIGH" OR severity="CRITICAL"
serviceAccount: service-org-123456789012@gcp-sa-scc-notification.iam.gserviceaccount.com

That command opens a live tap. Every HIGH or CRITICAL finding, the ones already sitting there and every new one after, gets published to a Pub/Sub topic as JSON (JavaScript Object Notation, the plain-text data format your scripts read). Pub/Sub is Google's message pipe: one system drops a message onto a named 'topic', and everything subscribed to that topic gets a copy, the way a public-address announcement reaches every speaker at once. The service account in the output is the identity SCC publishes as. Give it permission to publish to that topic (the Pub/Sub Publisher role) or nothing ever flows. Grant it once, and the wall starts feeding your automation.

How Event Threat Detection reads your logs

ETD deserves a slow read, because its model looks nothing like a scanner's. It never touches your resources. It drinks a stream of your logs, matches each entry against a library of detection rules plus Google's own threat intelligence (known-bad IP addresses, mining pools, malware domains it has seen in cloud DNS traffic), and raises a finding when a pattern lines up. Some of those logs cost you nothing. Admin Activity audit logs record every permission change and every resource creation, they are always on, and you cannot turn them off, so anything touching IAM (Identity and Access Management, the system that decides who may call which API, an API being the endpoint your tools and scripts talk to) stays visible. Worth keeping the line clean here: IAM settles who may make the call, while VPC Service Controls, from earlier in this course, settles where the call is allowed to come from. Data Access audit logs are the other half of the picture. They record the reads and the queries, and they are off by default. While they are off, ETD never sees those events at all, and a whole family of data-theft detections stays dark. You switch them on in the org's IAM policy.

policy.yaml — enable Data Access logs so ETD can see reads
# edited copy of: gcloud organizations get-iam-policy 123456789012 > policy.yaml
auditConfigs:
- service: allServices
auditLogConfigs:
- logType: ADMIN_READ
- logType: DATA_READ
- logType: DATA_WRITE
bindings:
- members:
role: roles/securitycenter.admin
etag: BwYX5f2r9lE=
$ gcloud organizations set-iam-policy 123456789012 policy.yaml
Updated IAM policy for organization [123456789012].
auditConfigs:
- auditLogConfigs:
- logType: ADMIN_READ
- logType: DATA_READ
- logType: DATA_WRITE
service: allServices
etag: BwYX5f3s0mF=
version: 1

One habit to respect: set-iam-policy replaces the entire org policy, not only the audit section. Run get-iam-policy first, edit the file it hands you, then push that same file back. Hand-write the bindings from scratch, drop one by accident, and you can lock people out of the whole tree with a single command. Read, change, write, in that order.

With Data Access logs off, ETD is half-blind
ETD catches what its logs contain and nothing more. Admin Activity logs are always on, so grants and role changes light up fine. The reads are the gap. Queries against BigQuery (Google's data warehouse) and object downloads, the events that spell exfiltration (attacker slang for copying data out the door), live in Data Access logs, and those ship disabled to keep log volume down. Turn on Premium, watch the anomalous-grant findings roll in, assume you are covered, and you will quietly miss the day somebody starts pulling data out. Enable DATA_READ at the org before you trust ETD on exfiltration, and budget for the extra log ingestion that brings.

Reading a real finding

The wall only helps if you can read what is on it. Pull the active threats as JSON and you get the raw finding, which is what your automation parses downstream anyway. Here is one that should ruin an on-call engineer's afternoon.

list active HIGH threats as JSON, then triage the payload
$ gcloud scc findings list "organizations/123456789012" \
--source=- \
--filter='state="ACTIVE" AND severity="HIGH" AND category="Persistence: IAM Anomalous Grant"' \
--format=json
[
{
"finding": {
"name": "organizations/123456789012/sources/1082504511739805707/findings/2f9c1b7e4a1e4d0b",
"parent": "organizations/123456789012/sources/1082504511739805707",
"resourceName": "//cloudresourcemanager.googleapis.com/projects/prod-payments-42",
"state": "ACTIVE",
"category": "Persistence: IAM Anomalous Grant",
"severity": "HIGH",
"findingClass": "THREAT",
"mute": "UNMUTED",
"eventTime": "2026-07-15T04:12:33.482Z",
"createTime": "2026-07-15T04:13:01.006Z",
"sourceProperties": {
"detectionCategory": {
"technique": "persistence",
"ruleName": "iam_anomalous_grant",
"indicator": "audit_log"
},
"evidence": [
{ "sourceLogId": { "projectId": "prod-payments-42", "insertId": "1a2b3c4d5e" } }
],
"properties": {
"sensitiveRoleGrant": {
"principalEmail": "[email protected]",
"bindingDeltas": [
{ "action": "ADD", "role": "roles/owner", "member": "user:[email protected]" }
]
}
}
}
},
"resource": {
"name": "//cloudresourcemanager.googleapis.com/projects/prod-payments-42",
"projectDisplayName": "prod-payments",
"parentDisplayName": "payments-team",
"type": "google.cloud.resourcemanager.Project"
}
}
]

Read it the way a responder would. findingClass says THREAT, so this is something in motion, not a setting that happens to be wrong. The category, Persistence: IAM Anomalous Grant, is ETD's name for a sensitive role landing on an unusual member. resourceName puts it in prod-payments-42, about the worst project it could have picked. Now the payload. principalEmail is svc-deployer, your CI service account (CI, continuous integration, the robot that builds and ships your code), and it added user:[email protected] as roles/owner. A build robot does not invite a random Gmail address to own production. That service account is compromised, full stop. Moves in order: revoke the owner grant, disable the deployer's keys, then pull every Admin Activity log entry for both identities across that window to work out what else they touched. Muting comes later, and only for the things that turn out to be harmless.

suppress a known-benign class without going blind
$ gcloud scc muteconfigs create static-site-public \
--organization=123456789012 \
--description="Public read on the marketing static-site bucket is intentional" \
--filter='category="PUBLIC_BUCKET_ACL" AND resource.project_display_name="marketing-site"'
Created mute config [organizations/123456789012/muteConfigs/static-site-public].
name: organizations/123456789012/muteConfigs/static-site-public
description: Public read on the marketing static-site bucket is intentional
filter: category="PUBLIC_BUCKET_ACL" AND resource.project_display_name="marketing-site"
createTime: '2026-07-15T05:40:12.771Z'
mostRecentEditor: [email protected]

A mute config deletes nothing. It marks matching findings so they drop out of your default view and stop paging you. The marketing static-site bucket really is meant to be public, so muting that one category on that one project keeps the wall honest without training yourself to ignore red. Scope your mutes tight, by project and by category. Mute PUBLIC_BUCKET_ACL (an ACL, access control list, is the permission list attached to a bucket or object) across the whole org and you will sleep straight through the next bucket that leaks real customer data by accident.

From the wall to a response pipeline

A finding sitting in a console is a dashboard nobody watches. The point of the Pub/Sub tap is that findings become inputs for code. A SIEM lines the anomalous grant up against the sign-in that happened seconds before it. A Cloud Function revokes the binding on its own within seconds. A ticket writes itself with the resourceName and severity already filled in. SCC hands you one ranked feed of everything wrong across the org. Turning that feed into action that happens without you is the next lesson.

SCC as the org control room
Signals (every project, org-wide)
Cloud Audit Logs
Admin Activity always on; Data Access off by default
VPC Flow + Cloud DNS logs
network and resolver activity
Resource config
buckets, firewall, IAM, KMS state
GKE node runtime
processes and binaries in pods
SCC detectors
Security Health Analytics
scans config for misconfig / posture
Event Threat Detection
mines the log stream for threats
Container Threat Detection
watches GKE runtime
One control-room wall
SCC findings
normalized, severity-scored, org-wide
Response pipeline
Pub/Sub tap
HIGH/CRITICAL streamed as JSON
Google SecOps / SIEM
correlate and investigate
Cloud Function
auto-revoke, quarantine, ticket
Signals on the left, three detectors reading them, one ranked wall of findings, then the response pipeline the next lesson automates.

You do not need every module switched on in week one. Pick the detectors that match what you are actually afraid of: posture scanning if your worry is sprawl and public buckets, ETD if it is stolen credentials, Container Threat Detection if you run Kubernetes. What you do need from day one is enablement at the organization node, so the project somebody spins up next Tuesday does not start dark. And keep mute rules honest. A mute is a risk that a named person accepted, with a ticket number attached to it. It is not a drawer for findings you have grown tired of seeing.

A finding is worth exactly as much as the path that follows it. Export to Pub/Sub, open a case in your tracker, page a human on the categories that mean persistence or exfiltration, and measure how long acknowledgement takes. The control room falls apart when the operator is asleep and nothing else is listening.

Try this

List the active findings for one project, pull a single finding as JSON, and check that Event Threat Detection is really producing findings from your audit logs.

terminal
gcloud scc findings list organizations/ORGANIZATION_ID \
--filter='state="ACTIVE" AND resource.project_display_name="payments-prod"' \
--limit=5 \
--format="table(finding.category,finding.severity,finding.eventTime)"
gcloud scc findings list organizations/ORGANIZATION_ID \
--filter='category="Persistence: IAM Anomalous Grant"' --limit=1 --format=json
output
CATEGORY SEVERITY EVENT_TIME
Persistence: IAM Anomalous Grant HIGH 2026-07-16T02:14:03Z
Public Bucket ACL MEDIUM 2026-07-15T18:02:11Z
MFA not enforced LOW 2026-07-14T09:44:50Z
[
{
"finding": {
"category": "Persistence: IAM Anomalous Grant",
"severity": "HIGH",
"state": "ACTIVE",
"sourceProperties": {"bindingDelta": "roles/owner"}
}
}
]

Takeaway

Two things are worth checking on your own org this week. First, that DATA_READ is enabled at the organization node, because without it ETD cannot see a single read and your exfiltration detections are decoration. Second, that every HIGH finding has somewhere to go other than a browser tab you keep meaning to close.

Next you wire that feed into code: Pub/Sub into a Cloud Function or your SOAR platform, so the sprinklers trip on their own instead of waiting for somebody to notice on Monday morning.

Quick check
01ETD is running org-wide on Premium and findings keep arriving, but you never see anything about data reads or a large BigQuery export. What is the most likely explanation?
Correct — ETD is log-driven. Admin Activity logs are always on, but Data Access logs, the ones recording reads and queries, have to be enabled in the org IAM policy before those detections can fire.
Incorrect — That describes Security Health Analytics, the posture scanner. ETD reads logs, not configuration.
Incorrect — Container Threat Detection only watches GKE node runtime. It has nothing to do with BigQuery reads.
Incorrect — No such rule exists. Findings stay ACTIVE until someone resolves them or a mute config you wrote matches them.
02Three detectors feed the SCC wall. Which one spots a reverse shell (a hijacked command line phoning home to an attacker) starting up inside a pod that is already running on GKE (Google Kubernetes Engine)?
Incorrect — Wrong one. It scans configuration state (open buckets, wide firewall rules, logging switched off) and never watches what a process does while it runs.
Incorrect — Wrong one. ETD mines the audit and network log stream for suspicious sequences; live behaviour inside a pod is not in that stream.
Correct — It sits on the GKE node runtime and flags live events like a reverse shell or an unexpected binary starting mid-run.
Incorrect — Wrong one. That is the basic web-app scanning in the Standard tier, unrelated to container runtime.
03You need to add one role binding at the organization node. A colleague suggests writing a short policy.yaml containing only that binding and applying it with gcloud organizations set-iam-policy. What is wrong with that plan?
Correct — It overwrites rather than merges, so the safe sequence is get-iam-policy, edit the file it returns, then set that same file back. Skip the read step and you can lock the whole org out.
Incorrect — It runs fine at the organization level; the lesson does exactly that. The real danger is the overwrite.
Incorrect — Org-level bindings do inherit down to folders and projects. Inheritance is not the problem here.
Incorrect — It does not merge. It replaces the full policy, which is precisely the footgun to avoid.

Related