Skip to content

09 · Security Deep Dive (Security Command Center, VPC Service Controls)

This module covers organization-level security tooling: Security Command Center for continuous threat/misconfiguration detection, and VPC Service Controls for perimeter-based data exfiltration protection — a different layer than IAM, which controls who can act; VPC-SC controls where data can flow, regardless of who's authorized.

Security Command Center (SCC)

SCC aggregates findings from GCP's built-in scanners (misconfigurations, vulnerabilities, active threats) into one dashboard, org-wide.

gcloud scc findings list organizations/123456789012 \
  --filter="state=\"ACTIVE\" AND severity=\"HIGH\""
# NAME                                    CATEGORY                   SEVERITY  STATE
# .../findings/a1b2c3                     PUBLIC_BUCKET_ACL          HIGH      ACTIVE
# .../findings/d4e5f6                     OPEN_FIREWALL              HIGH      ACTIVE
gcloud scc findings describe organizations/123456789012 \
  --finding=a1b2c3 --source=SOURCE_ID
# category: PUBLIC_BUCKET_ACL
# resourceName: //storage.googleapis.com/orders-exports-bucket
# recommendation: Remove allUsers/allAuthenticatedUsers from bucket IAM policy

Once remediated, mark the finding to keep the dashboard signal-to-noise ratio usable:

gcloud scc findings update organizations/123456789012 \
  --finding=a1b2c3 --source=SOURCE_ID --state=INACTIVE

Gotcha — SCC tiers gate what you get. The free Standard tier surfaces a limited set of findings (mostly asset inventory and a subset of misconfigurations); Premium tier adds threat detection (Event Threat Detection, Container Threat Detection) and vulnerability scanning. Assuming "SCC will catch it" without checking which tier is enabled at the org level is a common gap — gcloud scc settings describe organizations/123456789012 shows the active tier.

VPC Service Controls

A service perimeter wraps a set of projects and restricts API calls (GCS, BigQuery, etc.) to only work from within trusted networks/identities — even a compromised, fully-authorized credential can't exfiltrate data by copying it to an outside project, because the perimeter blocks the API call itself, not just the credential.

gcloud access-context-manager policies create \
  --organization=123456789012 \
  --title="corp-policy"

gcloud access-context-manager perimeters create prod-perimeter \
  --title="Production Data Perimeter" \
  --resources=projects/111111111111,projects/222222222222 \
  --restricted-services=storage.googleapis.com,bigquery.googleapis.com \
  --policy=POLICY_ID
gcloud access-context-manager perimeters describe prod-perimeter --policy=POLICY_ID
# title: Production Data Perimeter
# status:
#   resources: [projects/111111111111, projects/222222222222]
#   restrictedServices: [storage.googleapis.com, bigquery.googleapis.com]

Now, a gsutil cp attempting to copy from a bucket inside the perimeter to a bucket in a project outside it fails — 403 Request is prohibited by organization's policy — regardless of the caller's IAM roles, because the call crosses the perimeter boundary.

Gotcha — perimeters block legitimate cross-project workflows too, by design. A CI pipeline in an unrelated tooling project trying to read a protected BigQuery dataset breaks the same way a malicious exfiltration attempt would. The fix is an explicit ingress/egress rule allowing that specific caller identity and service combination — not disabling the perimeter:

gcloud access-context-manager perimeters update prod-perimeter \
  --policy=POLICY_ID \
  --add-egress-policies=egress-ci-readonly.yaml
# egress-ci-readonly.yaml
- egressFrom:
    identities:
      - serviceAccount:ci-sa@tooling-project.iam.gserviceaccount.com
  egressTo:
    operations:
      - serviceName: bigquery.googleapis.com
        methodSelectors:
          - method: "*"
    resources:
      - projects/111111111111

Gotcha — perimeters have a dry-run mode, and skipping it is how outages happen. Always deploy new/changed perimeters with --dry-run first; dry-run logs what would be blocked without actually blocking it, so you can find missing ingress/egress rules before they break production traffic:

gcloud access-context-manager perimeters dry-run enforce prod-perimeter --policy=POLICY_ID
gcloud logging read 'protoPayload.metadata.dryRun=true' --limit=20

Binary Authorization (brief)

Complementary control for the deploy path: only allow container images signed by a trusted attestor to run on GKE/Cloud Run.

gcloud container binauthz policy import policy.yaml

gcloud container binauthz attestations sign-and-create \
  --artifact-url=us-central1-docker.pkg.dev/my-project/app/orders@sha256:abc... \
  --attestor=prod-attestor \
  --attestor-project=my-project \
  --keyversion=projects/my-project/locations/us-central1/keyRings/binauthz/cryptoKeys/attestor-key/cryptoKeyVersions/1

Gotcha — Binary Authorization checks at deploy time, not build time. An unsigned image can still sit in Artifact Registry indefinitely; the block only triggers on gcloud run deploy / kubectl apply against a Binary-Authorization-enforced cluster or service. Don't mistake "the image exists in the registry" for "it passed policy."

Cheat sheet

Command Purpose
gcloud scc findings list List active security findings org-wide.
gcloud scc settings describe Check which SCC tier (Standard/Premium) is active.
gcloud access-context-manager perimeters create Wrap projects in a VPC-SC perimeter.
--add-egress-policies / --add-ingress-policies Punch a scoped exception through a perimeter.
perimeters dry-run enforce Test a perimeter's impact before enforcing it.
gcloud container binauthz policy import Require signed/attested images at deploy time.

How It Actually Works

VPC Service Controls and Binary Authorization enforce security at two different points in a resource's lifecycle. VPC Service Controls draws a service perimeter around a set of projects and blocks API-level data exfiltration by intercepting calls to protected services (BigQuery, GCS, etc.) and checking whether both the caller's identity and the network path it originated from are inside the perimeter — this is why VPC-SC can stop a compromised, fully-IAM-authorized service account from copying data to an external project: the perimeter check happens independently of and in addition to the IAM Checker's normal allow decision. Binary Authorization enforces at deploy time instead: it requires container images pushed to GKE or Cloud Run to carry a cryptographic attestation signed by a trusted authority (proving, say, that the image passed a vulnerability scan or a specific CI pipeline), and the admission controller rejects deployment of any image lacking a valid signature — this only works because container image digests are content-addressed (a SHA256 hash of the image content), so a signature over the digest can't be replayed against a different, tampered image.

Exercise

Design (command sequence, no live resources needed) a VPC Service Controls perimeter around two projects restricting storage.googleapis.com. Write the dry-run command you'd run first, describe what you'd look for in the dry-run logs, and write one egress rule allowing a specific CI service account read-only access to a bucket inside the perimeter from a project outside it.