Get started

GCP security collector

om scan gcp requires GCP_PROJECT_ID. Clients use Application Default Credentials (gcloud auth application-default login, GOOGLE_APPLICATION_CREDENTIALS, or the metadata server). The Compute client requests the cloud-platform scope. One scan covers one project.

export GCP_PROJECT_ID=my-project
gcloud auth application-default login
./om scan gcp
./om rules run

GCP_REGION (default us-central1) is the region segment on service account node ids and on the id of each bucket. Instance ids use the zone from the aggregated list. The bucket node’s region field is the bucket location, which can differ from the id segment.

Resources

API Graph
Compute instances.aggregatedList Workload. Properties: resource_id (self link), public_ip (NAT IP, or empty), status. A NAT IP or a public load balancer adds REACHABLE from internet:global when a firewall allow is not covered by a higher-priority deny. The firewall is a Network node. Each attached service account gets an ASSUMES edge from the instance.
Storage bucket list, then each bucket’s IAM policy Datastore with resource_id (bucket name), public_access, and sensitivity when a bucket label names it. Bucket IAM members that can read objects get CAN_ACCESS to that bucket.
IAM service accounts, then the project IAM policy Identity named by email, with email and admin_access. Project bindings that grant object access add CAN_ACCESS to every bucket in the project.
Cloud SQL instances Datastore with resource_id (connection name), service: cloudsql, public_access, and sensitivity when a user label names it. A workload gets CAN_ACCESS when it shares the instance’s private-IP network, or its public IP is in an authorized network.
Logging entries.list for the Admin Activity log, last 24 hours Up to five audit_events on the identity and the resource the event names. See below.

Node ids are gcp:{project}:{zone-or-region}:{kind}:{resource}. Instance resources use the instance name. Service account resources use uniqueId, so the displayed name (the email) can change without a new id.

When a bucket is public

public_access is true when the bucket IAM policy grants allUsers or allAuthenticatedUsers one of these roles:

  • roles/storage.admin, roles/storage.objectAdmin, roles/storage.objectViewer
  • legacy bucket owner, legacy object owner, legacy object reader
  • primitive owner, editor, or viewer

Public access prevention is not read. A bucket can have prevention unset and still be stored as private when those members are absent. allUsers on a role that cannot read objects, such as roles/storage.legacyBucketReader, does not set public_access. A label named sensitivity or data-class is stored as sensitivity. sensitivity wins when both are set, and a blank value is omitted. Cloud SQL copies the same property from the instance’s user labels.

Service accounts and admin

admin_access comes from project IAM, not from the display name or email. roles/owner, roles/editor, and roles/resourcemanager.projectIamAdmin are admin. A custom role is admin when its included permissions contain resourcemanager.projects.setIamPolicy. A binding with a condition does not set the flag and does not add CAN_ACCESS.

Known service accounts use uniqueId as the node id. An instance service account that is not in the project list uses the email as the id. Users, groups, and Workload Identity principals are not inventoried.

Edges

  • ASSUMES from the instance to each service account in serviceAccounts. The workload is the source.
  • CAN_ACCESS from a service account to a bucket when bucket IAM or project IAM grants object access. A bucket binding covers that bucket. A project binding covers every bucket in the scan.

Object access is roles/owner, roles/editor, the storage admin and object roles (objectAdmin, objectViewer, objectUser, and the legacy object reader/owner roles), or a custom role with storage.objects.get. roles/viewer is not object access. A public bucket with no such binding has no CAN_ACCESS edge, and a private bucket does when IAM grants it. An instance with a NAT IP is not linked to buckets unless its service account is. The bucket IAM read uses the v1 policy, so conditional bucket bindings are not included. A bucket policy that cannot be read is stored as not public and adds no CAN_ACCESS edges. The scan continues.

Cloud Audit Logs

After inventory is built, the scan lists Admin Activity log entries for the project from the last 24 hours. The call is Logging entries.list on projects/GCP_PROJECT_ID, filtered to cloudaudit.googleapis.com/activity, newest first, and it stops after four pages. A failed lookup omits audit_events and does not fail the scan.

An event is kept when its method is on a fixed admin list (for example v1.compute.instances.insert, storage.buckets.update, v1.compute.firewalls.patch, SetIamPolicy, cloudsql.instances.update) and its principal email matches an identity already in the batch. The resource has to sit on an exposed path: an internet-reachable workload, a node that workload assumes or can access, a network that workload affects, a public datastore, or an identity that can access a public datastore. Instance and firewall names match the self link stored on the node. A bucket event uses projects/_/buckets/NAME. A service-account key walks up to that identity. A project SetIamPolicy event is stored on the identity, because the project is not a node. The same event is stored on the identity and the resource, newest first, at most five per node.

GET /v1/graph/query and om paths run list those events in audits when the resource node is on the returned path. The console prints the same lines under the path. Data Access logs, including object reads, are a different log, so this slice does not collect them. CloudTrail events are collected by om scan aws. Activity Log events are collected by om scan azure.

Read access

The scan lists instances, firewalls, load-balancer pieces, buckets, bucket IAM, service accounts, the project IAM policy, Cloud SQL instances, and Admin Activity log entries. Listing those entries needs logging.logEntries.list. Logs Viewer (roles/logging.viewer) includes it. The collector does not call mutating APIs and does not request Data Access logs.